Compare commits
185
Commits
6a25f58be2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d8e4f3625 | ||
|
|
add2138321 | ||
|
|
05aff91858 | ||
|
|
e0ecd63dfd | ||
|
|
33ae6c57a2 | ||
|
|
07882506c0 | ||
|
|
9af4b778eb | ||
|
|
2f89d5cfdc | ||
|
|
0e15bfe4bf | ||
|
|
3d42a82003 | ||
|
|
4927e35bfc | ||
|
|
2b3acd5d51 | ||
|
|
6f2af1a40d | ||
|
|
6f0e3d6063 | ||
|
|
3e2819cf93 | ||
|
|
5e6ec1eff3 | ||
|
|
14e57acfd8 | ||
|
|
5cf8d18742 | ||
|
|
086da50a57 | ||
|
|
36a18f65c1 | ||
|
|
ea853c1ace | ||
|
|
448ad42005 | ||
|
|
80c37496e8 | ||
|
|
a75bb863ff | ||
|
|
dc8d2a49cd | ||
|
|
8b4a748c9d | ||
|
|
175251496f | ||
|
|
1fda63ae78 | ||
|
|
6c6ddf936a | ||
|
|
bd8367db0e | ||
|
|
b07b9d92d4 | ||
|
|
7a98f8e79f | ||
|
|
25287bc370 | ||
|
|
5eb5e2d51e | ||
|
|
c40650ab67 | ||
|
|
9758c3e425 | ||
|
|
7cff165f08 | ||
|
|
7825e87c19 | ||
|
|
04250a8605 | ||
|
|
5e419f501d | ||
|
|
1b7a736be2 | ||
|
|
6209251ca6 | ||
|
|
a118f3da3c | ||
|
|
8049477795 | ||
|
|
f08225136f | ||
|
|
bbb8691c90 | ||
|
|
da0954ffcc | ||
|
|
f3344a0fb9 | ||
|
|
eb25e98f79 | ||
|
|
5b85feec19 | ||
|
|
c4e7fa3375 | ||
|
|
b574a79dfe | ||
|
|
f58113e257 | ||
|
|
c90369181f | ||
|
|
187e082018 | ||
|
|
3837ebef06 | ||
|
|
94906028fc | ||
|
|
c59bfc20f7 | ||
|
|
04f5f42bb5 | ||
|
|
33df4bef39 | ||
|
|
e8c4b6eb11 | ||
|
|
15a524adbb | ||
|
|
c3776fb967 | ||
|
|
a55bb27641 | ||
|
|
0e93009e4b | ||
|
|
f536aefc99 | ||
|
|
9390f4fd31 | ||
|
|
7d2c2c52e2 | ||
|
|
bcea91a2c0 | ||
|
|
fc163f7f06 | ||
|
|
9857f11798 | ||
|
|
da5dce22e6 | ||
|
|
8345f67c50 | ||
|
|
bba2b30cbb | ||
|
|
deb9f29b47 | ||
|
|
be74d89fea | ||
|
|
75034a2c5a | ||
|
|
835481f4a5 | ||
|
|
9b3489676e | ||
|
|
dd209a2609 | ||
|
|
2408be335d | ||
|
|
42143fed71 | ||
|
|
8dd3f11d7b | ||
|
|
e3041bfdc7 | ||
|
|
4ce026ec78 | ||
|
|
4e6c5ca341 | ||
|
|
b4100113fe | ||
|
|
11fdc64447 | ||
|
|
a92ac29d19 | ||
|
|
07df4054da | ||
|
|
2915c1c1ec | ||
|
|
161be80fd6 | ||
|
|
c3f5df7a29 | ||
|
|
93eb2f4f6a | ||
|
|
99759b82b7 | ||
|
|
827286a0b2 | ||
|
|
9f31a04909 | ||
|
|
3e69abdfa0 | ||
|
|
a1bb1b93c9 | ||
|
|
7c1f95e850 | ||
|
|
0841e49b46 | ||
|
|
3ea87a1d28 | ||
|
|
4ea1242247 | ||
|
|
d6cd90ae4a | ||
|
|
b76223693b | ||
|
|
ad137576a8 | ||
|
|
5835c63bda | ||
|
|
aa283f8c75 | ||
|
|
39329caf22 | ||
|
|
a33f35faab | ||
|
|
aa5b5117ad | ||
|
|
51e788a5ad | ||
|
|
c7e4eb2575 | ||
|
|
55ed70353a | ||
|
|
abb075939f | ||
|
|
a3ab6aecfa | ||
|
|
097be4c012 | ||
|
|
ea7049306e | ||
|
|
fc3dc936fa | ||
|
|
f2ea84be0f | ||
|
|
003db7332d | ||
|
|
8797f695e0 | ||
|
|
74dd24a516 | ||
|
|
5c95288874 | ||
|
|
0c972f420c | ||
|
|
c5bbd7befd | ||
|
|
a209452a68 | ||
|
|
4487c800df | ||
|
|
d9cfd3f7c1 | ||
|
|
0465d5802a | ||
|
|
66774364fd | ||
|
|
3078f62162 | ||
|
|
8e92317f9e | ||
|
|
c3d6af85ba | ||
|
|
5d844554f5 | ||
|
|
a3d1cd1d74 | ||
|
|
ed5efdb0d6 | ||
|
|
4457a711cd | ||
|
|
925fdd474a | ||
|
|
d65f56bf03 | ||
|
|
4501061060 | ||
|
|
0aef4ee2c0 | ||
|
|
c1c9429a9d | ||
|
|
356cdaf051 | ||
|
|
b41bba72c9 | ||
|
|
a818c4489c | ||
|
|
8800ff4f7a | ||
|
|
6161d577c6 | ||
|
|
ddf0a2858b | ||
|
|
8c08b8a594 | ||
|
|
4a7ed92519 | ||
|
|
95e0ddf49a | ||
|
|
fdeba07fd4 | ||
|
|
a14cd1e0a5 | ||
|
|
061ed74d4f | ||
|
|
8fb1a1a8e9 | ||
|
|
bc01b34e58 | ||
|
|
68bc95fdf1 | ||
|
|
13d020a351 | ||
|
|
af678374bc | ||
|
|
e5978310bf | ||
|
|
8bfae9b483 | ||
|
|
9f7fe81ef6 | ||
|
|
aad95de5fd | ||
|
|
c385432bb0 | ||
|
|
5188d57d19 | ||
|
|
0880d98fe0 | ||
|
|
1c676f9548 | ||
|
|
cfe2987e50 | ||
|
|
0e966a6408 | ||
|
|
50baae0a66 | ||
|
|
4cac25b5b3 | ||
|
|
f4fe44ad02 | ||
|
|
db22cc9f06 | ||
|
|
3b4ea3f42d | ||
|
|
37be9506c8 | ||
|
|
ed13b1f6b5 | ||
|
|
4110680cc6 | ||
|
|
6f1f1c3fe7 | ||
|
|
b37a8087e9 | ||
|
|
4ff3092b2f | ||
|
|
832816a87e | ||
|
|
57d50a6dbb | ||
|
|
7f1558bc64 | ||
|
|
e00ff4ba6a |
+2
-1
@@ -17,6 +17,7 @@ AllowAllParametersOfDeclarationOnNextLine: false
|
|||||||
AllowShortLambdasOnASingleLine: All
|
AllowShortLambdasOnASingleLine: All
|
||||||
|
|
||||||
AlwaysBreakTemplateDeclarations: Yes
|
AlwaysBreakTemplateDeclarations: Yes
|
||||||
|
IndentPPDirectives: BeforeHash
|
||||||
|
|
||||||
BraceWrapping:
|
BraceWrapping:
|
||||||
AfterEnum: false
|
AfterEnum: false
|
||||||
@@ -32,7 +33,7 @@ SpaceAfterTemplateKeyword: false
|
|||||||
SpaceInEmptyBlock: false
|
SpaceInEmptyBlock: false
|
||||||
NamespaceIndentation: None
|
NamespaceIndentation: None
|
||||||
FixNamespaceComments: true
|
FixNamespaceComments: true
|
||||||
Standard: c++17
|
Standard: c++20
|
||||||
TabWidth: 4
|
TabWidth: 4
|
||||||
UseTab: AlignWithSpaces
|
UseTab: AlignWithSpaces
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "vendor/ruc"]
|
||||||
|
path = vendor/ruc
|
||||||
|
url = https://github.com/Riyyi/ruc
|
||||||
+29
-19
@@ -4,6 +4,9 @@
|
|||||||
# Set project name
|
# Set project name
|
||||||
set(PROJECT "manafiles")
|
set(PROJECT "manafiles")
|
||||||
|
|
||||||
|
# Unit tests
|
||||||
|
option(MANAFILES_BUILD_TESTS "Build the Manafiles test programs" ON)
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
|
||||||
@@ -15,7 +18,7 @@ include(GNUInstallDirs)
|
|||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Setup C++ compiler
|
# Setup C++ compiler
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
|
||||||
@@ -50,29 +53,23 @@ endif()
|
|||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Configure source
|
# Library
|
||||||
|
|
||||||
# Define include files
|
set(RUC_BUILD_TESTS OFF)
|
||||||
include_directories(
|
add_subdirectory("vendor/ruc")
|
||||||
"src"
|
|
||||||
"test"
|
# ------------------------------------------
|
||||||
)
|
# Application target
|
||||||
|
|
||||||
# Define source files
|
# Define source files
|
||||||
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
|
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
|
||||||
set(PROJECT_SOURCES ${PROJECT_SOURCES})
|
set(PROJECT_SOURCES ${PROJECT_SOURCES})
|
||||||
|
|
||||||
# Define test source files
|
|
||||||
file(GLOB_RECURSE TEST_SOURCES "test/*.cpp")
|
|
||||||
file(GLOB_RECURSE MAIN_SOURCES "src/*.cpp")
|
|
||||||
set(TEST_SOURCES ${TEST_SOURCES} ${MAIN_SOURCES})
|
|
||||||
list(REMOVE_ITEM TEST_SOURCES "${CMAKE_SOURCE_DIR}/src/main.cpp")
|
|
||||||
|
|
||||||
# ------------------------------------------
|
|
||||||
# Application target
|
|
||||||
|
|
||||||
add_executable(${PROJECT} ${PROJECT_SOURCES})
|
add_executable(${PROJECT} ${PROJECT_SOURCES})
|
||||||
target_link_libraries(${PROJECT})
|
target_include_directories(${PROJECT} PRIVATE
|
||||||
|
"src"
|
||||||
|
"vendor/ruc/src")
|
||||||
|
target_link_libraries(${PROJECT} ruc)
|
||||||
|
|
||||||
install(TARGETS ${PROJECT}
|
install(TARGETS ${PROJECT}
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
@@ -88,8 +85,21 @@ add_dependencies(run ${PROJECT})
|
|||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Unit test target
|
# Unit test target
|
||||||
|
|
||||||
add_executable(test ${TEST_SOURCES})
|
if (MANAFILES_BUILD_TESTS)
|
||||||
target_link_libraries(test)
|
# Define test source files
|
||||||
|
file(GLOB_RECURSE TEST_SOURCES "test/*.cpp")
|
||||||
|
set(TEST_SOURCES ${TEST_SOURCES} ${PROJECT_SOURCES})
|
||||||
|
list(REMOVE_ITEM TEST_SOURCES "${CMAKE_SOURCE_DIR}/src/main.cpp")
|
||||||
|
|
||||||
|
add_executable(${PROJECT}-unit-test ${TEST_SOURCES})
|
||||||
|
target_include_directories(${PROJECT}-unit-test PRIVATE
|
||||||
|
"src"
|
||||||
|
"test"
|
||||||
|
"vendor/ruc/src"
|
||||||
|
"vendor/ruc/test")
|
||||||
|
target_link_libraries(${PROJECT}-unit-test ruc)
|
||||||
|
target_link_libraries(${PROJECT}-unit-test ruc-test)
|
||||||
|
endif()
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Man page target
|
# Man page target
|
||||||
|
|||||||
+78
-34
@@ -16,7 +16,7 @@ Config file and package tracking utility.
|
|||||||
|
|
||||||
If you are using *Arch Linux*, you can install the AUR package [[https://aur.archlinux.org/packages/manafiles-git/][manafiles-git]].
|
If you are using *Arch Linux*, you can install the AUR package [[https://aur.archlinux.org/packages/manafiles-git/][manafiles-git]].
|
||||||
|
|
||||||
Alternatively, you will have to [[#building][build from source]].
|
Alternatively, you will have to [[#compiling][build from source]].
|
||||||
|
|
||||||
*** Configuration
|
*** Configuration
|
||||||
|
|
||||||
@@ -29,37 +29,45 @@ From there you can copy it to anywhere in the working directory,
|
|||||||
the config file is searched recursively. \\
|
the config file is searched recursively. \\
|
||||||
~$HOME/<dotfiles>/<anywhere>~
|
~$HOME/<dotfiles>/<anywhere>~
|
||||||
|
|
||||||
**** Exclude paths
|
**** Ignore patterns
|
||||||
|
|
||||||
Everything in this list will get excluded when pushing config files from the working directory to the system. \\
|
Everything in this list will get ignored when pulling/pushing config files from the working directory. \\
|
||||||
Currently three types of file matching are supported:
|
Currently two types of file matching are supported:
|
||||||
|
|
||||||
- ~file~ full name of the file matches
|
- ~literal~ matches a file or directory literally
|
||||||
- ~directory~ full name of the directory matches
|
- ~wildcard~ the asterisk matches zero or more characters
|
||||||
- ~endsWith~ end of the path matches
|
|
||||||
|
|
||||||
The excluded paths from the example config:
|
These behave similarly to a ~.gitignore~ pattern.
|
||||||
|
|
||||||
|
- If the pattern starts with a slash, it matches files and directories in the working directory root only.
|
||||||
|
- If the pattern doesn’t start with a slash, it matches files and directories in any directory or subdirectory.
|
||||||
|
- If the pattern ends with a slash, it matches only directories. When a directory is ignored, \\
|
||||||
|
all of its files and subdirectories are also ignored.
|
||||||
|
|
||||||
|
The ignore patterns from the example config:
|
||||||
#+BEGIN_SRC javascript
|
#+BEGIN_SRC javascript
|
||||||
"excludePaths" : {
|
"ignorePatterns" : [
|
||||||
".git": "directory",
|
".git/",
|
||||||
".md": "endsWith",
|
"*.md",
|
||||||
"manafiles.json": "endsWith",
|
"manafiles.json",
|
||||||
"packages": "file",
|
"packages",
|
||||||
"README.org": "endsWith",
|
"README.org",
|
||||||
"screenshot.png": "file"
|
"screenshot.png"
|
||||||
}
|
]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
**** System config files
|
**** System config files
|
||||||
|
|
||||||
Directories in this list will be pushed to the root ~/~ of the system, instead of ~$HOME~.
|
Everything in this list will be pushed to the root ~/~ of the system, instead of ~$HOME~. \\
|
||||||
|
These support the same file matching as explained in the [[#ignore-patterns][Ignore patterns]] section.
|
||||||
|
|
||||||
The system directories from the example config:
|
The system patterns from the example config:
|
||||||
#+BEGIN_SRC javascript
|
#+BEGIN_SRC javascript
|
||||||
"systemDirectories": [
|
"systemPatterns": [
|
||||||
"/boot",
|
"/boot/",
|
||||||
"/etc",
|
"/etc/",
|
||||||
"/usr/share"
|
"/usr/lib/",
|
||||||
|
"/usr/share/"
|
||||||
]
|
]
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
@@ -78,44 +86,80 @@ or uncomments them depending on the machine's configuration. Such a block looks
|
|||||||
like this:
|
like this:
|
||||||
|
|
||||||
#+BEGIN_SRC shell-script
|
#+BEGIN_SRC shell-script
|
||||||
# >>> distro=arch hostname=arch-desktop user=anon
|
# >>> distro=arch hostname=arch-desktop user=anon session=xorg
|
||||||
export PATH="$PATH:$HOME/.local/bin"
|
export PATH="$PATH:$HOME/.local/bin"
|
||||||
# <<<
|
# <<<
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
This block uses all of the supported configurations, ~distro~, ~hostname~ and
|
This block uses all of the supported configurations, ~distro~, ~hostname~,
|
||||||
~user~. These can be used in any combination and order.
|
~user~ and ~session~. These can be used in any combination and order.
|
||||||
|
|
||||||
If *all* of the given configurations match, manafiles will make sure that the
|
If *all* of the given configurations match, manafiles will make sure that the
|
||||||
contents of the block are uncommented. If *any* of them do *not* match, then
|
contents of the block are uncommented. If *any* of them do *not* match, then
|
||||||
manafiles will make sure that the contents of the block are commented.
|
manafiles will make sure that the contents of the block are commented.
|
||||||
|
|
||||||
** Building
|
***** Usable values for the configurations
|
||||||
|
|
||||||
*** Build dependencies
|
| distro | session |
|
||||||
|
|--------+---------|
|
||||||
|
| arch | xorg |
|
||||||
|
| debian | wayland |
|
||||||
|
| ubuntu | |
|
||||||
|
|
||||||
|
** Compiling
|
||||||
|
|
||||||
|
*** Dependencies
|
||||||
|
|
||||||
- ~gcc-libs~
|
- ~gcc-libs~
|
||||||
- (make) ~cmake~
|
- (make) ~cmake~
|
||||||
- (make) ~git~
|
- (make) ~git~
|
||||||
- (make) ~gzip~
|
- (make) ~gzip~
|
||||||
- (make) ~nlohmann-json~ [[https://archlinux.org/packages/community/any/nlohmann-json/][arch]], [[https://packages.debian.org/search?keywords=nlohmann-json][debian]], [[https://packages.ubuntu.com/search?keywords=nlohmann-json][ubuntu]]
|
|
||||||
- (optional) ~grep~
|
- (optional) ~grep~
|
||||||
- (optional) ~pacman~ + ~pacman-contrib~
|
- (optional) ~pacman~ + ~pacman-contrib~
|
||||||
- (optional) ~apt~ + ~dpkg~
|
- (optional) ~apt~ + ~dpkg~
|
||||||
|
|
||||||
The optional requirements are for the package tracking functionality.
|
The optional requirements are for the package tracking functionality.
|
||||||
|
|
||||||
*** Build compilation
|
*** Building
|
||||||
|
|
||||||
|
To get the sources from the repository, run the following commands:
|
||||||
|
|
||||||
|
#+BEGIN_SRC shell-script
|
||||||
|
$ git clone https://github.com/riyyi/manafiles
|
||||||
|
$ cd manafiles
|
||||||
|
$ git submodule init
|
||||||
|
$ git submodule update
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
#+BEGIN_SRC shell-script
|
||||||
|
$ git clone --recursive https://github.com/riyyi/manafiles
|
||||||
|
$ cd manafiles
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
**** Compiling
|
||||||
|
|
||||||
|
To compile, run the following commands:
|
||||||
|
|
||||||
#+BEGIN_SRC shell-script
|
#+BEGIN_SRC shell-script
|
||||||
$ mkdir build
|
$ mkdir build
|
||||||
$ cd build
|
$ cd build
|
||||||
$ cmake ..
|
$ cmake ..
|
||||||
$ make
|
$ make
|
||||||
#+END_SRC
|
# Optional. This will install the manafiles executable in /usr/bin
|
||||||
|
|
||||||
*** Build installation
|
|
||||||
|
|
||||||
#+BEGIN_SRC shell-script
|
|
||||||
$ sudo make install
|
$ sudo make install
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
*** Uninstalling
|
||||||
|
|
||||||
|
To uninstall, run the following commands:
|
||||||
|
|
||||||
|
*Warning*: ~make uninstall~ will try to delete all files listed in the
|
||||||
|
~install_manifest.txt~ file inside your ~build~ folder. Make sure you first
|
||||||
|
check that it won't delete any important files.
|
||||||
|
|
||||||
|
#+BEGIN_SRC shell-script
|
||||||
|
$ cd build
|
||||||
|
$ sudo make uninstall
|
||||||
|
#+END_SRC
|
||||||
|
|||||||
+6
-4
@@ -1,4 +1,4 @@
|
|||||||
.TH MANAFILES 1 "2022-01-29" "manafiles 0.5" "User Commands"
|
.TH MANAFILES 1 "2025-02-02" "manafiles 0.6" "User Commands"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
manafiles \- config file and package tracking utility
|
manafiles \- config file and package tracking utility
|
||||||
@@ -31,7 +31,7 @@ See File Options below.
|
|||||||
.TP
|
.TP
|
||||||
.BR \-P ", " \-\-package
|
.BR \-P ", " \-\-package
|
||||||
Operate on packages. \
|
Operate on packages. \
|
||||||
This operation allows you to track installed packages and reinstall them. \
|
This operation allows tracking installed packages and reinstalling them. \
|
||||||
In the first case, if no package names are provided in the command line, all packages will be selected. \
|
In the first case, if no package names are provided in the command line, all packages will be selected. \
|
||||||
See Package Options below.
|
See Package Options below.
|
||||||
|
|
||||||
@@ -67,8 +67,10 @@ Install all AUR packages of the stored list.
|
|||||||
Install all official packages of the stored list.
|
Install all official packages of the stored list.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BR \-s ", " \-\-store
|
.BR \-s ", " \-\-search
|
||||||
Stores a list of all installed packages on the system.
|
Search each locally-installed package for names via a partial match. \
|
||||||
|
When including multiple search terms, all packages with names matching any of those terms are returned. \
|
||||||
|
If the \fB-s\fR option is ommited, packages names are filtered via a full match.
|
||||||
|
|
||||||
.SH EXAMPLES
|
.SH EXAMPLES
|
||||||
Usage examples:
|
Usage examples:
|
||||||
|
|||||||
+13
-12
@@ -1,15 +1,16 @@
|
|||||||
{
|
{
|
||||||
"excludePaths" : {
|
"ignorePatterns": [
|
||||||
".git": "directory",
|
".git/",
|
||||||
".md": "endsWith",
|
"*.md",
|
||||||
"manafiles.json": "endsWith",
|
"manafiles.json",
|
||||||
"packages": "file",
|
"packages",
|
||||||
"README.org": "endsWith",
|
"README.org",
|
||||||
"screenshot.png": "file"
|
"screenshot.png"
|
||||||
},
|
],
|
||||||
"systemDirectories": [
|
"systemPatterns": [
|
||||||
"/boot",
|
"/boot/",
|
||||||
"/etc",
|
"/etc/",
|
||||||
"/usr/share"
|
"/usr/lib/",
|
||||||
|
"/usr/share/"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-20
@@ -11,9 +11,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "ruc/json/json.h"
|
||||||
|
#include "ruc/meta/assert.h"
|
||||||
|
|
||||||
Config::Config(s)
|
Config::Config(s)
|
||||||
: m_workingDirectory(std::filesystem::current_path())
|
: m_workingDirectory(std::filesystem::current_path())
|
||||||
@@ -51,7 +51,7 @@ void Config::parseConfigFile()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json json;
|
ruc::Json json;
|
||||||
|
|
||||||
std::ifstream file(m_config);
|
std::ifstream file(m_config);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
@@ -72,29 +72,23 @@ void Config::parseConfigFile()
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
void to_json(nlohmann::json& object, const Settings& settings)
|
void toJson(ruc::Json& json, const Settings& settings)
|
||||||
{
|
{
|
||||||
object = nlohmann::json {
|
json = ruc::Json {
|
||||||
{ "excludePaths", settings.excludePaths },
|
{ "ignorePatterns", settings.ignorePatterns },
|
||||||
{ "systemDirectories", settings.systemDirectories }
|
{ "systemPatterns", settings.systemPatterns }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void from_json(const nlohmann::json& object, Settings& settings)
|
void fromJson(const ruc::Json& json, Settings& settings)
|
||||||
{
|
{
|
||||||
if (object.find("excludePaths") != object.end()) {
|
VERIFY(json.type() == ruc::Json::Type::Object);
|
||||||
object.at("excludePaths").get_to(settings.excludePaths);
|
|
||||||
|
if (json.exists("ignorePatterns")) {
|
||||||
|
json.at("ignorePatterns").getTo(settings.ignorePatterns);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object.find("systemDirectories") != object.end()) {
|
if (json.exists("systemPatterns")) {
|
||||||
object.at("systemDirectories").get_to(settings.systemDirectories);
|
json.at("systemPatterns").getTo(settings.systemPatterns);
|
||||||
}
|
|
||||||
|
|
||||||
// Check for correct exclude type values
|
|
||||||
for (const auto& path : settings.excludePaths) {
|
|
||||||
if (path.second != "file" && path.second != "directory" && path.second != "endsWith") {
|
|
||||||
fprintf(stderr, "\033[31;1mConfig:\033[0m invalid exclude type '%s'\n", path.second.c_str());
|
|
||||||
raise(SIGABRT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-26
@@ -4,45 +4,44 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef CONFIG_H
|
#pragma once
|
||||||
#define CONFIG_H
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <filesystem> // path
|
#include <filesystem> // path
|
||||||
#include <map>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "nlohmann/json.hpp"
|
#include "ruc/json/json.h"
|
||||||
|
#include "ruc/singleton.h"
|
||||||
#include "util/singleton.h"
|
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
std::map<std::string, std::string> excludePaths {
|
std::vector<std::string> ignorePatterns {
|
||||||
{ ".git", "directory" },
|
".git/",
|
||||||
{ ".md", "endsWith" },
|
"*.md",
|
||||||
{ "packages", "file" },
|
"manafiles.json",
|
||||||
{ "README.org", "endsWith" },
|
"packages",
|
||||||
{ "screenshot.png", "file" },
|
"README.org",
|
||||||
|
"screenshot.png",
|
||||||
};
|
};
|
||||||
std::vector<std::filesystem::path> systemDirectories {
|
std::vector<std::string> systemPatterns {
|
||||||
"/boot",
|
"/boot/",
|
||||||
"/etc",
|
"/etc/",
|
||||||
"/usr/share"
|
"/usr/lib/",
|
||||||
|
"/usr/share/"
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class Config : public Util::Singleton<Config> {
|
class Config : public ruc::Singleton<Config> {
|
||||||
public:
|
public:
|
||||||
Config(s);
|
Config(s);
|
||||||
virtual ~Config();
|
virtual ~Config();
|
||||||
|
|
||||||
void setSystemDirectories(const std::vector<std::filesystem::path>& systemDirectories) { m_settings.systemDirectories = systemDirectories; }
|
void setSystemPatterns(const std::vector<std::string>& systemPatterns) { m_settings.systemPatterns = systemPatterns; }
|
||||||
void setExcludePaths(const std::map<std::string, std::string>& excludePaths) { m_settings.excludePaths = excludePaths; }
|
void setIgnorePatterns(const std::vector<std::string>& ignorePatterns) { m_settings.ignorePatterns = ignorePatterns; }
|
||||||
void setVerbose(bool verbose) { m_verbose = verbose; }
|
void setVerbose(bool verbose) { m_verbose = verbose; }
|
||||||
|
|
||||||
const std::map<std::string, std::string>& excludePaths() const { return m_settings.excludePaths; }
|
const std::vector<std::string>& ignorePatterns() const { return m_settings.ignorePatterns; }
|
||||||
const std::vector<std::filesystem::path>& systemDirectories() const { return m_settings.systemDirectories; }
|
const std::vector<std::string>& systemPatterns() const { return m_settings.systemPatterns; }
|
||||||
|
|
||||||
const std::filesystem::path& workingDirectory() const { return m_workingDirectory; }
|
const std::filesystem::path& workingDirectory() const { return m_workingDirectory; }
|
||||||
size_t workingDirectorySize() const { return m_workingDirectorySize; }
|
size_t workingDirectorySize() const { return m_workingDirectorySize; }
|
||||||
@@ -64,9 +63,7 @@ private:
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
// nlohmann::json arbitrary type conversion functions
|
// Json arbitrary type conversion functions
|
||||||
|
|
||||||
void to_json(nlohmann::json& object, const Settings& settings);
|
void toJson(ruc::Json& object, const Settings& settings);
|
||||||
void from_json(const nlohmann::json& object, Settings& settings);
|
void fromJson(const ruc::Json& object, Settings& settings);
|
||||||
|
|
||||||
#endif // CONFIG_H
|
|
||||||
|
|||||||
+191
-78
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
* Copyright (C) 2021-2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -15,10 +15,12 @@
|
|||||||
#include <unistd.h> // geteuid, getlogin, setegid, seteuid
|
#include <unistd.h> // geteuid, getlogin, setegid, seteuid
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ruc/file.h"
|
||||||
|
#include "ruc/meta/assert.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "dotfile.h"
|
#include "dotfile.h"
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
#include "util/file.h"
|
|
||||||
|
|
||||||
Dotfile::Dotfile(s)
|
Dotfile::Dotfile(s)
|
||||||
{
|
{
|
||||||
@@ -50,7 +52,7 @@ void Dotfile::add(const std::vector<std::string>& targets)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSystemTarget(targets.at(i))) {
|
if (match(targets.at(i), Config::the().systemPatterns())) {
|
||||||
systemIndices.push_back(i);
|
systemIndices.push_back(i);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -63,11 +65,10 @@ void Dotfile::add(const std::vector<std::string>& targets)
|
|||||||
for (size_t i : noExistIndices) {
|
for (size_t i : noExistIndices) {
|
||||||
fprintf(stderr, "\033[31;1mDotfile:\033[0m '%s': no such file or directory\n", targets.at(i).c_str());
|
fprintf(stderr, "\033[31;1mDotfile:\033[0m '%s': no such file or directory\n", targets.at(i).c_str());
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sync(
|
sync(
|
||||||
SyncType::Pull, targets, homeIndices, systemIndices,
|
SyncType::Add, targets, homeIndices, systemIndices,
|
||||||
[](std::string* paths, const std::string& homePath, const std::string& homeDirectory) {
|
[](std::string* paths, const std::string& homePath, const std::string& homeDirectory) {
|
||||||
paths[0] = homePath;
|
paths[0] = homePath;
|
||||||
paths[1] = homePath.substr(homeDirectory.size() + 1);
|
paths[1] = homePath.substr(homeDirectory.size() + 1);
|
||||||
@@ -105,6 +106,135 @@ void Dotfile::push(const std::vector<std::string>& targets)
|
|||||||
pullOrPush(SyncType::Push, targets);
|
pullOrPush(SyncType::Push, targets);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Dotfile::match(const std::string& path, const std::vector<std::string>& patterns)
|
||||||
|
{
|
||||||
|
VERIFY(path.front() == '/', "path is not absolute: '{}'", path);
|
||||||
|
|
||||||
|
// Cut off working directory
|
||||||
|
size_t cutFrom = path.find(Config::the().workingDirectory()) == 0 ? Config::the().workingDirectorySize() : 0;
|
||||||
|
std::string pathString = path.substr(cutFrom);
|
||||||
|
|
||||||
|
for (const auto& pattern : patterns) {
|
||||||
|
// A dot matches everything in the current working directory
|
||||||
|
if (pattern == ".") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exact match is obviously true
|
||||||
|
if (pathString == pattern) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If starts with '/', only match in the working directory root
|
||||||
|
bool onlyMatchInRoot = false;
|
||||||
|
if (pattern.front() == '/') {
|
||||||
|
onlyMatchInRoot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If ends with '/', only match directories
|
||||||
|
bool onlyMatchDirectories = false;
|
||||||
|
if (pattern.back() == '/') {
|
||||||
|
onlyMatchDirectories = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsing
|
||||||
|
|
||||||
|
bool tryPatternState = true;
|
||||||
|
|
||||||
|
size_t pathIterator = 0;
|
||||||
|
size_t patternIterator = 0;
|
||||||
|
|
||||||
|
if (!onlyMatchInRoot) {
|
||||||
|
pathIterator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current path charter 'x' == next ignore pattern characters '*x'
|
||||||
|
// Example, iterator at []: [.]log/output.txt
|
||||||
|
// [*].log
|
||||||
|
if (pathIterator < pathString.length()
|
||||||
|
&& patternIterator < pattern.length() - 1
|
||||||
|
&& pattern.at(patternIterator) == '*'
|
||||||
|
&& pathString.at(pathIterator) == pattern.at(patternIterator + 1)) {
|
||||||
|
patternIterator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; pathIterator < pathString.length() && patternIterator < pattern.length();) {
|
||||||
|
char character = pathString.at(pathIterator);
|
||||||
|
pathIterator++;
|
||||||
|
|
||||||
|
if (!tryPatternState && character == '/') {
|
||||||
|
tryPatternState = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tryPatternState) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character == pattern.at(patternIterator)) {
|
||||||
|
// Fail if the final match hasn't reached the end of the ignore pattern
|
||||||
|
// Example, iterator at []: doc/buil[d]
|
||||||
|
// buil[d]/
|
||||||
|
if (pathIterator == pathString.length() && patternIterator < pattern.length() - 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next path character 'x' == next ignore pattern characters '*x', skip the '*'
|
||||||
|
// Example, iterator at []: /includ[e]/header.h
|
||||||
|
// /includ[e]*/
|
||||||
|
if (pathIterator < pathString.length()
|
||||||
|
&& patternIterator < pattern.length() - 2
|
||||||
|
&& pattern.at(patternIterator + 1) == '*'
|
||||||
|
&& pathString.at(pathIterator) == pattern.at(patternIterator + 2)) {
|
||||||
|
patternIterator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
patternIterator++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.at(patternIterator) == '*') {
|
||||||
|
// Fail if we're entering a subdirectory and we should only match in the root
|
||||||
|
// Example, iterator at []: /src[/]include/header.h
|
||||||
|
// /[*]include/
|
||||||
|
if (onlyMatchInRoot && character == '/') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next path character == next ignore pattern character
|
||||||
|
if (pathIterator < pathString.length()
|
||||||
|
&& patternIterator + 1 < pattern.length()
|
||||||
|
&& pathString.at(pathIterator) == pattern.at(patternIterator + 1)) {
|
||||||
|
patternIterator++;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset filter pattern if it hasnt been completed at this point
|
||||||
|
// Example, iterator at []: /[s]rc/include/header.h
|
||||||
|
// /[i]nclude*/
|
||||||
|
if (patternIterator < pattern.length() - 1) {
|
||||||
|
patternIterator = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryPatternState = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patternIterator == pattern.length()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (pattern.back() == '*' && patternIterator == pattern.length() - 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (onlyMatchDirectories && patternIterator == pattern.length() - 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
void Dotfile::pullOrPush(SyncType type, const std::vector<std::string>& targets)
|
void Dotfile::pullOrPush(SyncType type, const std::vector<std::string>& targets)
|
||||||
@@ -116,7 +246,7 @@ void Dotfile::pullOrPush(SyncType type, const std::vector<std::string>& targets)
|
|||||||
// Separate home and system targets
|
// Separate home and system targets
|
||||||
forEachDotfile(targets, [&](const std::filesystem::directory_entry& path, size_t index) {
|
forEachDotfile(targets, [&](const std::filesystem::directory_entry& path, size_t index) {
|
||||||
dotfiles.push_back(path.path().string());
|
dotfiles.push_back(path.path().string());
|
||||||
if (isSystemTarget(path.path().string())) {
|
if (match(path.path().string(), Config::the().systemPatterns())) {
|
||||||
systemIndices.push_back(index);
|
systemIndices.push_back(index);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -167,9 +297,8 @@ void Dotfile::sync(SyncType type,
|
|||||||
if (!systemIndices.empty() && !root) {
|
if (!systemIndices.empty() && !root) {
|
||||||
for (size_t i : systemIndices) {
|
for (size_t i : systemIndices) {
|
||||||
fprintf(stderr, "\033[31;1mDotfile:\033[0m need root privileges to copy system file '%s'\n",
|
fprintf(stderr, "\033[31;1mDotfile:\033[0m need root privileges to copy system file '%s'\n",
|
||||||
paths.at(i).c_str() + Config::the().workingDirectorySize());
|
paths.at(i).c_str() + (type == SyncType::Add ? 0 : Config::the().workingDirectorySize()));
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto printError = [](const std::filesystem::path& path, const std::error_code& error) -> void {
|
auto printError = [](const std::filesystem::path& path, const std::error_code& error) -> void {
|
||||||
@@ -250,41 +379,60 @@ void Dotfile::sync(SyncType type,
|
|||||||
|
|
||||||
void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
||||||
{
|
{
|
||||||
Util::File dotfile(path);
|
ruc::File dotfile(path);
|
||||||
|
|
||||||
const std::string search[3] = {
|
const std::string search[4] = {
|
||||||
"distro=",
|
"distro=",
|
||||||
"hostname=",
|
"hostname=",
|
||||||
"user=",
|
"user=",
|
||||||
|
"session=",
|
||||||
};
|
};
|
||||||
|
|
||||||
// State of the loop
|
// State of the loop
|
||||||
bool isFiltering = false;
|
bool isFiltering = false;
|
||||||
std::string filter[3];
|
std::string filter[4];
|
||||||
std::string commentCharacter;
|
std::string commentCharacter;
|
||||||
std::string commentTerminationCharacter;
|
std::string commentTerminationCharacter;
|
||||||
size_t positionInFile = 0;
|
size_t positionInFile = 0;
|
||||||
|
|
||||||
auto commentOrUncommentLine = [&](std::string& line, bool addComment) {
|
auto commentOrUncommentLine = [&](std::string& line, bool addComment) {
|
||||||
|
size_t indentation = line.find_first_not_of(" \t");
|
||||||
|
|
||||||
|
// Empty lines are skipped
|
||||||
|
if (line.empty() || indentation == std::string::npos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
size_t lineLength = line.size();
|
size_t lineLength = line.size();
|
||||||
size_t whiteSpaceBeforeComment = line.find_first_not_of(" \t");
|
size_t commentStart = line.find(commentCharacter, indentation);
|
||||||
size_t contentAfterComment = line.find_first_not_of(" \t" + commentCharacter);
|
size_t commentEnd = line.rfind(commentTerminationCharacter);
|
||||||
// NOTE: The +1 is needed to take the newline into account
|
bool hasComment = commentStart != std::string::npos && (commentTerminationCharacter.empty() || commentEnd != std::string::npos);
|
||||||
size_t contentLength = lineLength + 1 - contentAfterComment - (lineLength - line.find_last_not_of(" \t" + commentTerminationCharacter));
|
|
||||||
|
|
||||||
// If there was no comment, grab whitespace correctly
|
// Lines that have a comment at the *end* of the line aren't considered commented lines
|
||||||
if (whiteSpaceBeforeComment == std::string::npos) {
|
if (hasComment && line.find_first_not_of(" \t") < commentStart) {
|
||||||
whiteSpaceBeforeComment = contentAfterComment;
|
hasComment = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!addComment) {
|
// Uncomment line
|
||||||
line = line.substr(0, whiteSpaceBeforeComment)
|
if (hasComment && !addComment) {
|
||||||
+ line.substr(contentAfterComment, contentLength);
|
size_t contentStart = line.find_first_not_of(" \t", commentStart + commentCharacter.size());
|
||||||
|
size_t contentLength = commentEnd - contentStart;
|
||||||
|
|
||||||
|
// Trim trailing whitespace
|
||||||
|
std::string content = line.substr(contentStart, contentLength);
|
||||||
|
content = content.substr(0, content.find_last_not_of(" \t") + 1);
|
||||||
|
|
||||||
|
line = line.substr(0, indentation)
|
||||||
|
+ content;
|
||||||
}
|
}
|
||||||
else {
|
// Comment line
|
||||||
line = line.substr(0, whiteSpaceBeforeComment)
|
else if (!hasComment && addComment) {
|
||||||
|
size_t contentStart = line.find_first_not_of(" \t");
|
||||||
|
size_t contentLength = line.size() - contentStart;
|
||||||
|
|
||||||
|
line = line.substr(0, indentation)
|
||||||
+ commentCharacter + ' '
|
+ commentCharacter + ' '
|
||||||
+ line.substr(contentAfterComment, contentLength)
|
+ line.substr(contentStart, contentLength)
|
||||||
+ (!commentTerminationCharacter.empty() ? ' ' + commentTerminationCharacter : "");
|
+ (!commentTerminationCharacter.empty() ? ' ' + commentTerminationCharacter : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,7 +445,7 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
|||||||
if (line.find(">>>") != std::string::npos) {
|
if (line.find(">>>") != std::string::npos) {
|
||||||
// Find machine info
|
// Find machine info
|
||||||
size_t find = 0;
|
size_t find = 0;
|
||||||
for (size_t i = 0; i < 3; ++i) {
|
for (size_t i = 0; i < 4; ++i) {
|
||||||
find = line.find(search[i]) + search[i].size();
|
find = line.find(search[i]) + search[i].size();
|
||||||
if (find < search[i].size()) {
|
if (find < search[i].size()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -312,6 +460,10 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
|||||||
if (i > 0 && commentCharacter.at(i - 1) == '/' && commentCharacter.at(i) == '*') {
|
if (i > 0 && commentCharacter.at(i - 1) == '/' && commentCharacter.at(i) == '*') {
|
||||||
commentTerminationCharacter = "*/";
|
commentTerminationCharacter = "*/";
|
||||||
}
|
}
|
||||||
|
// Support for <!-- XMl comments -->
|
||||||
|
if (i > 0 && commentCharacter.at(i - 1) == '<' && commentCharacter.at(i) == '!' && commentCharacter.at(i + 1) == '-' && commentCharacter.at(i + 2) == '-') {
|
||||||
|
commentTerminationCharacter = "-->";
|
||||||
|
}
|
||||||
// NOTE: Modification of the string should be at the end of the iteration to prevent 'out of range' errors
|
// NOTE: Modification of the string should be at the end of the iteration to prevent 'out of range' errors
|
||||||
if (commentCharacter.at(i) == ' ' || commentCharacter.at(i) == '\t') {
|
if (commentCharacter.at(i) == ' ' || commentCharacter.at(i) == '\t') {
|
||||||
commentCharacter.erase(i, 1);
|
commentCharacter.erase(i, 1);
|
||||||
@@ -327,6 +479,7 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
|||||||
filter[0] = "";
|
filter[0] = "";
|
||||||
filter[1] = "";
|
filter[1] = "";
|
||||||
filter[2] = "";
|
filter[2] = "";
|
||||||
|
filter[3] = "";
|
||||||
commentCharacter.clear();
|
commentCharacter.clear();
|
||||||
commentTerminationCharacter.clear();
|
commentTerminationCharacter.clear();
|
||||||
continue;
|
continue;
|
||||||
@@ -348,6 +501,10 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
|||||||
commentOrUncommentLine(line, true);
|
commentOrUncommentLine(line, true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
else if (filter[3] != Machine::the().session() && !filter[3].empty()) {
|
||||||
|
commentOrUncommentLine(line, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
commentOrUncommentLine(line, false);
|
commentOrUncommentLine(line, false);
|
||||||
}
|
}
|
||||||
@@ -359,63 +516,19 @@ void Dotfile::forEachDotfile(const std::vector<std::string>& targets, const std:
|
|||||||
{
|
{
|
||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
for (const auto& path : std::filesystem::recursive_directory_iterator { Config::the().workingDirectory() }) {
|
for (const auto& path : std::filesystem::recursive_directory_iterator { Config::the().workingDirectory() }) {
|
||||||
if (path.is_directory() || filter(path)) {
|
std::string pathString = path.path().string();
|
||||||
|
|
||||||
|
if (path.is_directory()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!targets.empty() && !include(path.path().string(), targets)) {
|
// Ignore pattern check
|
||||||
|
if (match(pathString, Config::the().ignorePatterns())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Include check
|
||||||
|
if (!targets.empty() && !match(pathString, targets)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
callback(path, index++);
|
callback(path, index++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Dotfile::filter(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
for (auto& excludePath : Config::the().excludePaths()) {
|
|
||||||
if (excludePath.second == "file") {
|
|
||||||
if (path.string() == Config::the().workingDirectory() / excludePath.first) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (excludePath.second == "directory") {
|
|
||||||
if (path.string().find(Config::the().workingDirectory() / excludePath.first) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (excludePath.second == "endsWith") {
|
|
||||||
if (path.string().find(excludePath.first) == path.string().size() - excludePath.first.size()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Dotfile::include(const std::filesystem::path& path, const std::vector<std::string>& targets)
|
|
||||||
{
|
|
||||||
for (const auto& target : targets) {
|
|
||||||
if (path.string().find(Config::the().workingDirectory() / target) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Dotfile::isSystemTarget(const std::string& target)
|
|
||||||
{
|
|
||||||
for (const auto& systemDirectory : Config::the().systemDirectories()) {
|
|
||||||
|
|
||||||
if (target.find(systemDirectory) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// FIXME: The second filesystem::path cant have a "/" as the first character,
|
|
||||||
// as it will think the path is at the root.
|
|
||||||
if (target.find(Config::the().workingDirectory().string() + systemDirectory.string()) == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|||||||
+8
-10
@@ -4,23 +4,24 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef DOTFILE_H
|
#pragma once
|
||||||
#define DOTFILE_H
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
|
#include <cstdint> // uint8_t
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <functional> // function
|
#include <functional> // function
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
|
|
||||||
class Dotfile : public Util::Singleton<Dotfile> {
|
class Dotfile : public ruc::Singleton<Dotfile> {
|
||||||
public:
|
public:
|
||||||
Dotfile(s);
|
Dotfile(s);
|
||||||
virtual ~Dotfile();
|
virtual ~Dotfile();
|
||||||
|
|
||||||
enum class SyncType {
|
enum class SyncType : uint8_t {
|
||||||
|
Add,
|
||||||
Pull,
|
Pull,
|
||||||
Push,
|
Push,
|
||||||
};
|
};
|
||||||
@@ -30,6 +31,8 @@ public:
|
|||||||
void pull(const std::vector<std::string>& targets = {});
|
void pull(const std::vector<std::string>& targets = {});
|
||||||
void push(const std::vector<std::string>& targets = {});
|
void push(const std::vector<std::string>& targets = {});
|
||||||
|
|
||||||
|
bool match(const std::string& path, const std::vector<std::string>& patterns);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void pullOrPush(SyncType type, const std::vector<std::string>& targets = {});
|
void pullOrPush(SyncType type, const std::vector<std::string>& targets = {});
|
||||||
void sync(SyncType type,
|
void sync(SyncType type,
|
||||||
@@ -39,9 +42,4 @@ private:
|
|||||||
void selectivelyCommentOrUncomment(const std::string& path);
|
void selectivelyCommentOrUncomment(const std::string& path);
|
||||||
|
|
||||||
void forEachDotfile(const std::vector<std::string>& targets, const std::function<void(const std::filesystem::directory_entry&, size_t)>& callback);
|
void forEachDotfile(const std::vector<std::string>& targets, const std::function<void(const std::filesystem::directory_entry&, size_t)>& callback);
|
||||||
bool filter(const std::filesystem::path& path);
|
|
||||||
bool include(const std::filesystem::path& path, const std::vector<std::string>& targets);
|
|
||||||
bool isSystemTarget(const std::string& target);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DOTFILE_H
|
|
||||||
|
|||||||
+90
-6
@@ -1,21 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <pwd.h> // getpwnam
|
#include <cstdint> // int8_t
|
||||||
#include <sstream> // istringstream
|
#include <filesystem> // std::filesystem::path
|
||||||
#include <unistd.h> // gethostname, getlogin
|
#include <pwd.h> // getpwnam
|
||||||
|
#include <sstream> // istringstream
|
||||||
|
#include <unistd.h> // gethostname, getlogin
|
||||||
|
|
||||||
|
#include "ruc/file.h"
|
||||||
|
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
#include "util/file.h"
|
|
||||||
|
|
||||||
Machine::Machine(s)
|
Machine::Machine(s)
|
||||||
{
|
{
|
||||||
fetchDistro();
|
fetchDistro();
|
||||||
fetchHostname();
|
fetchHostname();
|
||||||
fetchUsername();
|
fetchUsername();
|
||||||
|
fetchSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
Machine::~Machine()
|
Machine::~Machine()
|
||||||
@@ -26,7 +30,7 @@ Machine::~Machine()
|
|||||||
|
|
||||||
void Machine::fetchDistro()
|
void Machine::fetchDistro()
|
||||||
{
|
{
|
||||||
Util::File osRelease("/etc/os-release");
|
ruc::File osRelease("/etc/os-release");
|
||||||
std::istringstream stream(osRelease.data());
|
std::istringstream stream(osRelease.data());
|
||||||
for (std::string line; std::getline(stream, line);) {
|
for (std::string line; std::getline(stream, line);) {
|
||||||
if (line.find("ID=") == 0) {
|
if (line.find("ID=") == 0) {
|
||||||
@@ -61,3 +65,83 @@ void Machine::fetchUsername()
|
|||||||
perror("\033[31;1mError:\033[0m getpwnam");
|
perror("\033[31;1mError:\033[0m getpwnam");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Machine::fetchSession()
|
||||||
|
{
|
||||||
|
// Determine if this is an Xorg or Wayland session
|
||||||
|
int8_t likelyWayland = 0;
|
||||||
|
|
||||||
|
// Detect via environment variable
|
||||||
|
const char* env;
|
||||||
|
env = std::getenv("XDG_SESSION_TYPE");
|
||||||
|
if (env != nullptr) {
|
||||||
|
auto session = std::string(env);
|
||||||
|
if (session == "wayland") {
|
||||||
|
likelyWayland++;
|
||||||
|
}
|
||||||
|
else if (session == "x11") {
|
||||||
|
likelyWayland--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env = std::getenv("WAYLAND_DISPLAY");
|
||||||
|
if (env != nullptr) {
|
||||||
|
auto display = std::string(env);
|
||||||
|
if (display.find("wayland-", 0) == 0) {
|
||||||
|
likelyWayland++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect via Wayland socket
|
||||||
|
auto socket = std::filesystem::path("/run/user") / std::to_string(uid());
|
||||||
|
if (std::filesystem::exists(socket) && std::filesystem::is_directory(socket)) {
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(socket)) {
|
||||||
|
if (entry.path().filename().string().find("wayland-", 0) == 0) {
|
||||||
|
likelyWayland++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect via Xorg socket
|
||||||
|
if (std::filesystem::exists("/tmp/.X11-unix")) {
|
||||||
|
likelyWayland--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect via running processes, /proc/<id>/comm
|
||||||
|
std::filesystem::path processes = "/proc";
|
||||||
|
if (std::filesystem::exists(processes) && std::filesystem::is_directory(processes)) {
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(processes)) {
|
||||||
|
if (std::filesystem::is_directory(entry)) {
|
||||||
|
|
||||||
|
std::filesystem::path comm = entry.path() / "comm";
|
||||||
|
if (!std::filesystem::exists(comm)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the contents of the "comm" file
|
||||||
|
std::ifstream stream(comm);
|
||||||
|
std::string command;
|
||||||
|
std::getline(stream, command);
|
||||||
|
|
||||||
|
if (command == "Xwayland" || command == "sway" || command == "hyprland") {
|
||||||
|
likelyWayland++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command == "Xorg" || command == "xinit" || command == "i3" || command == "bspwm") {
|
||||||
|
likelyWayland--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we detected at least 2 ways, we can be fairly certain
|
||||||
|
m_session = "";
|
||||||
|
if (likelyWayland <= -2) {
|
||||||
|
m_session = "xorg";
|
||||||
|
}
|
||||||
|
else if (likelyWayland >= 2) {
|
||||||
|
m_session = "wayland";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+13
-12
@@ -4,37 +4,38 @@
|
|||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef MACHINE_H
|
#pragma once
|
||||||
#define MACHINE_H
|
|
||||||
|
|
||||||
#include <cstdint> // uint32_t
|
#include <cstdint> // uint32_t
|
||||||
#include <pwd.h> // passwd
|
#include <pwd.h> // passwd
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "util/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
|
|
||||||
class Machine : public Util::Singleton<Machine> {
|
class Machine : public ruc::Singleton<Machine> {
|
||||||
public:
|
public:
|
||||||
Machine(s);
|
Machine(s);
|
||||||
virtual ~Machine();
|
virtual ~Machine();
|
||||||
|
|
||||||
const std::string& distroId() { return m_distroId; }
|
const std::string& distroId() const { return m_distroId; }
|
||||||
const std::string& distroIdLike() { return m_distroIdLike; }
|
const std::string& distroIdLike() const { return m_distroIdLike; }
|
||||||
const std::string& hostname() { return m_hostname; }
|
const std::string& hostname() const { return m_hostname; }
|
||||||
|
|
||||||
std::string username() { return m_passwd->pw_name; }
|
std::string username() const { return std::string(m_passwd->pw_name); }
|
||||||
uint32_t uid() { return m_passwd->pw_uid; }
|
uint32_t uid() const { return m_passwd->pw_uid; }
|
||||||
uint32_t gid() { return m_passwd->pw_gid; }
|
uint32_t gid() const { return m_passwd->pw_gid; }
|
||||||
|
|
||||||
|
const std::string& session() const { return m_session; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void fetchDistro();
|
void fetchDistro();
|
||||||
void fetchHostname();
|
void fetchHostname();
|
||||||
void fetchUsername();
|
void fetchUsername();
|
||||||
|
void fetchSession();
|
||||||
|
|
||||||
std::string m_distroId;
|
std::string m_distroId;
|
||||||
std::string m_distroIdLike;
|
std::string m_distroIdLike;
|
||||||
std::string m_hostname;
|
std::string m_hostname;
|
||||||
|
std::string m_session;
|
||||||
passwd* m_passwd { nullptr };
|
passwd* m_passwd { nullptr };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MACHINE_H
|
|
||||||
|
|||||||
+18
-20
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
* Copyright (C) 2021-2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -8,11 +8,12 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ruc/argparser.h"
|
||||||
|
#include "ruc/timer.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "dotfile.h"
|
#include "dotfile.h"
|
||||||
#include "package.h"
|
#include "package.h"
|
||||||
#include "util/argparser.h"
|
|
||||||
#include "util/timer.h"
|
|
||||||
|
|
||||||
int main(int argc, const char* argv[])
|
int main(int argc, const char* argv[])
|
||||||
{
|
{
|
||||||
@@ -23,12 +24,12 @@ int main(int argc, const char* argv[])
|
|||||||
bool addOrAur = false;
|
bool addOrAur = false;
|
||||||
bool install = false;
|
bool install = false;
|
||||||
bool pull = false;
|
bool pull = false;
|
||||||
bool pushOrStore = false;
|
bool pushOrSearch = false;
|
||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
|
|
||||||
std::vector<std::string> targets {};
|
std::vector<std::string> targets {};
|
||||||
|
|
||||||
Util::ArgParser argParser;
|
ruc::ArgParser argParser;
|
||||||
argParser.addOption(fileOperation, 'F', "file", nullptr, nullptr);
|
argParser.addOption(fileOperation, 'F', "file", nullptr, nullptr);
|
||||||
argParser.addOption(packageOperation, 'P', "package", nullptr, nullptr);
|
argParser.addOption(packageOperation, 'P', "package", nullptr, nullptr);
|
||||||
argParser.addOption(helpOperation, 'h', "help", nullptr, nullptr);
|
argParser.addOption(helpOperation, 'h', "help", nullptr, nullptr);
|
||||||
@@ -36,10 +37,10 @@ int main(int argc, const char* argv[])
|
|||||||
argParser.addOption(addOrAur, 'a', "add", nullptr, nullptr);
|
argParser.addOption(addOrAur, 'a', "add", nullptr, nullptr);
|
||||||
argParser.addOption(install, 'i', "install", nullptr, nullptr);
|
argParser.addOption(install, 'i', "install", nullptr, nullptr);
|
||||||
argParser.addOption(pull, 'l', "pull", nullptr, nullptr);
|
argParser.addOption(pull, 'l', "pull", nullptr, nullptr);
|
||||||
argParser.addOption(pushOrStore, 's', "push", nullptr, nullptr);
|
argParser.addOption(pushOrSearch, 's', "push", nullptr, nullptr);
|
||||||
argParser.addOption(verbose, 'v', "verbose", nullptr, nullptr);
|
argParser.addOption(verbose, 'v', "verbose", nullptr, nullptr);
|
||||||
|
|
||||||
argParser.addArgument(targets, "targets", nullptr, nullptr, Util::ArgParser::Required::No);
|
argParser.addArgument(targets, "targets", nullptr, nullptr, ruc::ArgParser::Required::No);
|
||||||
argParser.parse(argc, argv);
|
argParser.parse(argc, argv);
|
||||||
|
|
||||||
if (fileOperation + packageOperation + helpOperation > 1) {
|
if (fileOperation + packageOperation + helpOperation > 1) {
|
||||||
@@ -48,7 +49,7 @@ int main(int argc, const char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
Util::Timer t;
|
ruc::Timer t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Config::the().setVerbose(verbose);
|
Config::the().setVerbose(verbose);
|
||||||
@@ -60,36 +61,33 @@ int main(int argc, const char* argv[])
|
|||||||
if (pull) {
|
if (pull) {
|
||||||
Dotfile::the().pull(targets);
|
Dotfile::the().pull(targets);
|
||||||
}
|
}
|
||||||
if (pushOrStore) {
|
if (pushOrSearch) {
|
||||||
Dotfile::the().push(targets);
|
Dotfile::the().push(targets);
|
||||||
}
|
}
|
||||||
if (!addOrAur && !pull && !pushOrStore) {
|
if (!addOrAur && !pull && !pushOrSearch) {
|
||||||
Dotfile::the().list(targets);
|
Dotfile::the().list(targets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (packageOperation) {
|
else if (packageOperation) {
|
||||||
if (addOrAur) {
|
if (addOrAur) {
|
||||||
Package::the().aurInstall();
|
Package::the().aurInstall(targets);
|
||||||
}
|
}
|
||||||
if (install) {
|
if (install) {
|
||||||
Package::the().install();
|
Package::the().install(targets);
|
||||||
}
|
}
|
||||||
if (pushOrStore) {
|
if (!addOrAur && !install) {
|
||||||
Package::the().store();
|
Package::the().list(targets, pushOrSearch);
|
||||||
}
|
|
||||||
if (!addOrAur && !install && !pushOrStore) {
|
|
||||||
Package::the().list(targets);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (helpOperation) {
|
else if (helpOperation) {
|
||||||
// TODO open manpage
|
// TODO: open manpage
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO open manpage
|
// TODO: open manpage
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
printf("%fms\n", t.elapsedNanoseconds() / 1000000.0);
|
printf("%fms\n", t.elapsedNanoseconds() / 1000000.0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+27
-31
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
* Copyright (C) 2021-2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
#include <string> // getline
|
#include <string> // getline
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ruc/shell.h"
|
||||||
|
#include "ruc/system.h"
|
||||||
|
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
#include "package.h"
|
#include "package.h"
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/shell.h"
|
|
||||||
#include "util/system.h"
|
|
||||||
|
|
||||||
Package::Package(s)
|
Package::Package(s)
|
||||||
{
|
{
|
||||||
@@ -30,17 +30,27 @@ Package::~Package()
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
void Package::aurInstall()
|
void Package::aurInstall(const std::vector<std::string>& targets)
|
||||||
{
|
{
|
||||||
installOrAurInstall(InstallType::AurInstall);
|
if (targets.size() > 1) {
|
||||||
|
fprintf(stderr, "\033[31;1mPackage:\033[0m only 1 file can be read packages from at a time\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
installOrAurInstall(InstallType::AurInstall, targets.size() != 0 ? targets.front() : PACKAGE_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Package::install()
|
void Package::install(const std::vector<std::string>& targets)
|
||||||
{
|
{
|
||||||
installOrAurInstall(InstallType::Install);
|
if (targets.size() > 1) {
|
||||||
|
fprintf(stderr, "\033[31;1mPackage:\033[0m only 1 file can be read packages from at a time\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
installOrAurInstall(InstallType::Install, targets.size() != 0 ? targets.front() : PACKAGE_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Package::list(const std::vector<std::string>& targets)
|
void Package::list(const std::vector<std::string>& targets, bool partialMatch)
|
||||||
{
|
{
|
||||||
auto packagesOrEmpty = getPackageList();
|
auto packagesOrEmpty = getPackageList();
|
||||||
|
|
||||||
@@ -62,7 +72,7 @@ void Package::list(const std::vector<std::string>& targets)
|
|||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(stream, line)) {
|
while (std::getline(stream, line)) {
|
||||||
for (const auto& target : targets) {
|
for (const auto& target : targets) {
|
||||||
if (line.find(target) != std::string::npos) {
|
if (line == target || (partialMatch && line.find(target) != std::string::npos)) {
|
||||||
packages.append(line + '\n');
|
packages.append(line + '\n');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -72,20 +82,6 @@ void Package::list(const std::vector<std::string>& targets)
|
|||||||
printf("%s", packages.c_str());
|
printf("%s", packages.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Package::store()
|
|
||||||
{
|
|
||||||
auto packagesOrEmpty = getPackageList();
|
|
||||||
|
|
||||||
if (!packagesOrEmpty.has_value()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto packageFile = Util::File::create("./packages");
|
|
||||||
packageFile.clear();
|
|
||||||
packageFile.append(packagesOrEmpty.value());
|
|
||||||
packageFile.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
std::optional<std::string> Package::fetchAurHelper()
|
std::optional<std::string> Package::fetchAurHelper()
|
||||||
@@ -105,7 +101,7 @@ std::optional<std::string> Package::fetchAurHelper()
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Package::installOrAurInstall(InstallType type)
|
void Package::installOrAurInstall(InstallType type, const std::string& file)
|
||||||
{
|
{
|
||||||
distroDetect();
|
distroDetect();
|
||||||
distroDependencies();
|
distroDependencies();
|
||||||
@@ -126,15 +122,15 @@ void Package::installOrAurInstall(InstallType type)
|
|||||||
|
|
||||||
std::string command = "";
|
std::string command = "";
|
||||||
|
|
||||||
Util::System $;
|
ruc::System $;
|
||||||
if (m_distro == Distro::Arch) {
|
if (m_distro == Distro::Arch) {
|
||||||
// Grab everything off enabled official repositories that is in the list
|
// Grab everything off enabled official repositories that is in the list
|
||||||
auto repoList = $("pacman -Ssq") | $("grep -xf ./packages");
|
auto repoList = $("pacman -Ssq") | $("grep -xf " + file);
|
||||||
|
|
||||||
if (type == InstallType::AurInstall) {
|
if (type == InstallType::AurInstall) {
|
||||||
// Determine which packages in the list are from the AUR
|
// Determine which packages in the list are from the AUR
|
||||||
// NOTE: Util::System does not support commands with newlines
|
// NOTE: ruc::System does not support commands with newlines
|
||||||
auto aurList = Util::Shell()("grep -vx '" + repoList.output() + "' ./packages");
|
auto aurList = ruc::Shell()("grep -vx '" + repoList.output() + "' " + file);
|
||||||
command = aurHelper.value() + " -Sy --devel --needed --noconfirm " + aurList.output();
|
command = aurHelper.value() + " -Sy --devel --needed --noconfirm " + aurList.output();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -143,7 +139,7 @@ void Package::installOrAurInstall(InstallType type)
|
|||||||
}
|
}
|
||||||
else if (m_distro == Distro::Debian) {
|
else if (m_distro == Distro::Debian) {
|
||||||
// Grab everything off enabled official repositories that is in the list
|
// Grab everything off enabled official repositories that is in the list
|
||||||
auto repoList = $("apt-cache search .").cut(1, ' ') | $("grep -xf ./packages");
|
auto repoList = $("apt-cache search .").cut(1, ' ') | $("grep -xf " + file);
|
||||||
command = "apt install " + repoList.output();
|
command = "apt install " + repoList.output();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +220,7 @@ std::optional<std::string> Package::getPackageList()
|
|||||||
|
|
||||||
std::string packages;
|
std::string packages;
|
||||||
|
|
||||||
Util::System $;
|
ruc::System $;
|
||||||
if (m_distro == Distro::Arch) {
|
if (m_distro == Distro::Arch) {
|
||||||
auto basePackages = $("pactree -u base").tail(2, true);
|
auto basePackages = $("pactree -u base").tail(2, true);
|
||||||
auto develPackages = $("pacman -Qqg base-devel");
|
auto develPackages = $("pacman -Qqg base-devel");
|
||||||
|
|||||||
+13
-14
@@ -1,42 +1,43 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
* Copyright (C) 2021-2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef PACKAGE_H
|
#pragma once
|
||||||
#define PACKAGE_H
|
|
||||||
|
|
||||||
|
#include <cstdint> // uint8_t
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
|
|
||||||
class Package : public Util::Singleton<Package> {
|
#define PACKAGE_FILE "./packages"
|
||||||
|
|
||||||
|
class Package : public ruc::Singleton<Package> {
|
||||||
public:
|
public:
|
||||||
Package(s);
|
Package(s);
|
||||||
virtual ~Package();
|
virtual ~Package();
|
||||||
|
|
||||||
enum class Distro {
|
enum class Distro : uint8_t {
|
||||||
Unsupported,
|
Unsupported,
|
||||||
Arch,
|
Arch,
|
||||||
Debian,
|
Debian,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class InstallType {
|
enum class InstallType : uint8_t {
|
||||||
Install,
|
Install,
|
||||||
AurInstall,
|
AurInstall,
|
||||||
};
|
};
|
||||||
|
|
||||||
void aurInstall();
|
void aurInstall(const std::vector<std::string>& targets = {});
|
||||||
void install();
|
void install(const std::vector<std::string>& targets = {});
|
||||||
void list(const std::vector<std::string>& targets = {});
|
void list(const std::vector<std::string>& targets = {}, bool partialMatch = false);
|
||||||
void store();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<std::string> fetchAurHelper();
|
std::optional<std::string> fetchAurHelper();
|
||||||
void installOrAurInstall(InstallType type);
|
void installOrAurInstall(InstallType type, const std::string& file);
|
||||||
|
|
||||||
bool findDependency(const std::string& search);
|
bool findDependency(const std::string& search);
|
||||||
bool distroDetect();
|
bool distroDetect();
|
||||||
@@ -45,5 +46,3 @@ private:
|
|||||||
|
|
||||||
Distro m_distro { Distro::Unsupported };
|
Distro m_distro { Distro::Unsupported };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // PACKAGE_H
|
|
||||||
|
|||||||
@@ -1,567 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm> // find_if
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdio> // printf
|
|
||||||
#include <cstring> // strcmp
|
|
||||||
#include <limits> // numeric_limits
|
|
||||||
#include <string> // stod, stoi, stoul
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util/argparser.h"
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
ArgParser::ArgParser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgParser::~ArgParser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::printError(char parameter, Error error)
|
|
||||||
{
|
|
||||||
char tmp[] { parameter, '\0' };
|
|
||||||
printError(tmp, error, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::printError(const char* parameter, Error error, bool longName)
|
|
||||||
{
|
|
||||||
if (!m_errorReporting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error == Error::OptionInvalid) {
|
|
||||||
printf("%s: invalid option -- '%s'\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
else if (error == Error::OptionUnrecognized) {
|
|
||||||
printf("%s: unrecognized option -- '%s'\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
else if (error == Error::OptionDoesntAllowArgument) {
|
|
||||||
printf("%s: option '--%s' doesn't allow an argument\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
else if (error == Error::OptionRequiresArgument) {
|
|
||||||
if (longName) {
|
|
||||||
printf("%s: option '--%s' requires an argument\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf("%s: option requires an argument -- '%s'\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (error == Error::OptionInvalidArgumentType) {
|
|
||||||
if (longName) {
|
|
||||||
printf("%s: option '--%s' invalid type\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf("%s: option invalid type -- '%s'\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (error == Error::ArgumentExtraOperand) {
|
|
||||||
printf("%s: extra operand '%s'\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
else if (error == Error::ArgumentRequired) {
|
|
||||||
printf("%s: missing required argument '%s'\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
else if (error == Error::ArgumentInvalidType) {
|
|
||||||
printf("%s: invalid argument type '%s'\n", m_name, parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Print command usage, if it's enabled.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required: directly after || separated by space
|
|
||||||
// Optional: directly after
|
|
||||||
bool ArgParser::parseShortOption(std::string_view option, std::string_view next)
|
|
||||||
{
|
|
||||||
bool result = true;
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
printf("Parsing short option: '%s'\n", option.data());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
char c;
|
|
||||||
std::string_view value;
|
|
||||||
for (size_t i = 0; i < option.size(); ++i) {
|
|
||||||
c = option.at(i);
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
printf("short '%c'\n", c);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&c](Option& it) -> bool {
|
|
||||||
return it.shortName == c;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Option does not exist
|
|
||||||
if (foundOption == m_options.cend()) {
|
|
||||||
printError(c, Error::OptionInvalid);
|
|
||||||
|
|
||||||
if (m_exitOnFirstError) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (foundOption->requiresArgument == Required::No) {
|
|
||||||
// FIXME: Figure out why providing a nullptr breaks the lambda here.
|
|
||||||
result = foundOption->acceptValue("");
|
|
||||||
}
|
|
||||||
else if (foundOption->requiresArgument == Required::Yes) {
|
|
||||||
value = option.substr(i + 1);
|
|
||||||
if (value.empty() && next.empty()) {
|
|
||||||
foundOption->error = Error::OptionRequiresArgument;
|
|
||||||
printError(c, Error::OptionRequiresArgument);
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
else if (!value.empty()) {
|
|
||||||
result = foundOption->acceptValue(value.data());
|
|
||||||
if (!result) {
|
|
||||||
printError(c, Error::OptionInvalidArgumentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (next[0] == '-') {
|
|
||||||
foundOption->error = Error::OptionRequiresArgument;
|
|
||||||
printError(c, Error::OptionRequiresArgument);
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
result = foundOption->acceptValue(next.data());
|
|
||||||
m_optionIndex++;
|
|
||||||
if (!result) {
|
|
||||||
printError(c, Error::OptionInvalidArgumentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (foundOption->requiresArgument == Required::Optional) {
|
|
||||||
value = option.substr(i + 1);
|
|
||||||
if (!value.empty()) {
|
|
||||||
result = foundOption->acceptValue(value.data());
|
|
||||||
if (!result) {
|
|
||||||
printError(c, Error::OptionInvalidArgumentType);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required: directly after, separated by '=' || separated by space
|
|
||||||
// Optional: directly after, separated by '='
|
|
||||||
bool ArgParser::parseLongOption(std::string_view option, std::string_view next)
|
|
||||||
{
|
|
||||||
std::string name = std::string(option.substr(0, option.find_first_of('=')));
|
|
||||||
std::string_view value = option.substr(option.find_first_of('=') + 1);
|
|
||||||
|
|
||||||
auto foundOption = std::find_if(m_options.begin(), m_options.end(), [&name](Option& it) -> bool {
|
|
||||||
return it.longName && it.longName == name;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (foundOption == m_options.cend()) {
|
|
||||||
printError(name.data(), Error::OptionUnrecognized);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class ArgumentProvided {
|
|
||||||
No,
|
|
||||||
DirectlyAfter,
|
|
||||||
Seperated,
|
|
||||||
};
|
|
||||||
|
|
||||||
auto argument = ArgumentProvided::No;
|
|
||||||
if (name != value || option.find('=') != std::string_view::npos) {
|
|
||||||
argument = ArgumentProvided::DirectlyAfter;
|
|
||||||
}
|
|
||||||
else if (!next.empty() && next[0] != '-') {
|
|
||||||
argument = ArgumentProvided::Seperated;
|
|
||||||
value = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool result = true;
|
|
||||||
|
|
||||||
if (foundOption->requiresArgument == Required::No) {
|
|
||||||
if (argument == ArgumentProvided::DirectlyAfter) {
|
|
||||||
foundOption->error = Error::OptionDoesntAllowArgument;
|
|
||||||
printError(name.data(), Error::OptionDoesntAllowArgument);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = foundOption->acceptValue("");
|
|
||||||
}
|
|
||||||
else if (foundOption->requiresArgument == Required::Yes) {
|
|
||||||
if (argument == ArgumentProvided::No) {
|
|
||||||
foundOption->error = Error::OptionRequiresArgument;
|
|
||||||
printError(name.data(), Error::OptionRequiresArgument);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = foundOption->acceptValue(value.data());
|
|
||||||
if (!result) {
|
|
||||||
printError(name.data(), Error::OptionInvalidArgumentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument == ArgumentProvided::Seperated) {
|
|
||||||
m_optionIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (foundOption->requiresArgument == Required::Optional) {
|
|
||||||
if (argument == ArgumentProvided::DirectlyAfter) {
|
|
||||||
result = foundOption->acceptValue(value.data());
|
|
||||||
if (!result) {
|
|
||||||
printError(name.data(), Error::OptionInvalidArgumentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ArgParser::parseArgument(std::string_view argument)
|
|
||||||
{
|
|
||||||
bool result = true;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
// Run out of argument handlers
|
|
||||||
if (m_argumentIndex >= m_arguments.size()) {
|
|
||||||
printError(argument.data(), Error::ArgumentExtraOperand);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Argument& currentArgument = m_arguments.at(m_argumentIndex);
|
|
||||||
result = currentArgument.acceptValue(argument.data());
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
currentArgument.addedValues++;
|
|
||||||
if (currentArgument.addedValues >= currentArgument.maxValues) {
|
|
||||||
m_argumentIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (currentArgument.minValues == 0) {
|
|
||||||
m_argumentIndex++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printError(argument.data(), Error::ArgumentInvalidType);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ArgParser::parse(int argc, const char* argv[])
|
|
||||||
{
|
|
||||||
bool result = true;
|
|
||||||
|
|
||||||
// Set looping indices
|
|
||||||
m_optionIndex = 1;
|
|
||||||
m_argumentIndex = 0;
|
|
||||||
|
|
||||||
// By default parse all '-' prefixed parameters as options
|
|
||||||
m_nonOptionMode = false;
|
|
||||||
|
|
||||||
// Get program name
|
|
||||||
m_name = argv[0] + std::string_view(argv[0]).find_last_of('/') + 1;
|
|
||||||
|
|
||||||
std::string_view argument;
|
|
||||||
std::string_view next;
|
|
||||||
for (; m_optionIndex < (size_t)argc; ++m_optionIndex) {
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
printf("argv[%zu]: %s\n", m_optionIndex, argv[m_optionIndex]);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Get the current and next parameter
|
|
||||||
argument = argv[m_optionIndex];
|
|
||||||
if (m_optionIndex + 1 < (size_t)argc && argv[m_optionIndex + 1][0] != '-') {
|
|
||||||
next = argv[m_optionIndex + 1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
next = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop parsing '-' prefixed parameters as options afer '--'
|
|
||||||
if (argument.compare("--") == 0) {
|
|
||||||
m_nonOptionMode = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Long Option
|
|
||||||
if (!m_nonOptionMode && argument[0] == '-' && argument[1] == '-') {
|
|
||||||
argument = argument.substr(argument.find_first_not_of('-'));
|
|
||||||
if (!parseLongOption(argument, next)) {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Short Option
|
|
||||||
else if (!m_nonOptionMode && argument[0] == '-') {
|
|
||||||
argument = argument.substr(argument.find_first_not_of('-'));
|
|
||||||
if (!parseShortOption(argument, next)) {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Argument
|
|
||||||
else {
|
|
||||||
if (m_stopParsingOnFirstNonOption) {
|
|
||||||
m_nonOptionMode = true;
|
|
||||||
}
|
|
||||||
if (!parseArgument(argument)) {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_exitOnFirstError && !result) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check any leftover arguments for required
|
|
||||||
for (; m_argumentIndex < m_arguments.size(); ++m_argumentIndex) {
|
|
||||||
Argument& currentArgument = m_arguments.at(m_argumentIndex);
|
|
||||||
if (currentArgument.minValues > currentArgument.addedValues) {
|
|
||||||
result = false;
|
|
||||||
printError(currentArgument.name ? currentArgument.name : "", Error::ArgumentRequired);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& option : m_options) {
|
|
||||||
if (option.longName && strcmp(option.longName, "help") == 0) {
|
|
||||||
printf("Try '%s --help' for more information.\n", m_name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (option.shortName == 'h') {
|
|
||||||
printf("Try '%s -h' for more information.\n", m_name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void ArgParser::addOption(Option&& option)
|
|
||||||
{
|
|
||||||
m_options.push_back(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, nullptr, usageString, manString, Required::No, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(const char*& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(std::string_view& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(unsigned int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(double& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(std::vector<std::string>& values, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(values) });
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void ArgParser::addArgument(Argument&& argument)
|
|
||||||
{
|
|
||||||
m_arguments.push_back(argument);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(bool& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(const char*& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(std::string& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(std::string_view& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(int& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(unsigned int& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(double& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(std::vector<std::string>& values, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, values.max_size(), 0, getAcceptFunction(values) });
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(bool& value)
|
|
||||||
{
|
|
||||||
return [&value](const char*) -> bool {
|
|
||||||
value = true;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(const char*& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
value = input;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(std::string& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
value = input;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(std::string_view& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
value = input;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(int& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
const char* validate = input;
|
|
||||||
for (; *validate != '\0'; ++validate) {
|
|
||||||
// - [0-9]
|
|
||||||
if (*validate != 45 && (*validate < 48 || *validate > 57)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
value = std::stoi(input);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(unsigned int& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
const char* validate = input;
|
|
||||||
for (; *validate != '\0'; ++validate) {
|
|
||||||
// [0-9]
|
|
||||||
if (*validate < 48 || *validate > 57) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long convert = 0;
|
|
||||||
try {
|
|
||||||
convert = std::stoul(input);
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (convert <= std::numeric_limits<unsigned int>::max()) {
|
|
||||||
value = static_cast<unsigned int>(convert);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(double& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
const char* validate = input;
|
|
||||||
for (; *validate != '\0'; ++validate) {
|
|
||||||
// . [0-9]
|
|
||||||
if (*validate != 46 && (*validate < 48 || *validate > 57)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
value = std::stod(input);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(std::vector<std::string>& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
value.push_back(input);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
#ifndef ARG_PARSER_H
|
|
||||||
#define ARG_PARSER_H
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <functional> // function
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
using AcceptFunction = std::function<bool(const char*)>;
|
|
||||||
|
|
||||||
class ArgParser final {
|
|
||||||
public:
|
|
||||||
ArgParser();
|
|
||||||
virtual ~ArgParser();
|
|
||||||
|
|
||||||
enum class Required {
|
|
||||||
No,
|
|
||||||
Yes,
|
|
||||||
Optional,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Error {
|
|
||||||
None,
|
|
||||||
OptionInvalid, // For short options
|
|
||||||
OptionUnrecognized, // For long options
|
|
||||||
OptionDoesntAllowArgument,
|
|
||||||
OptionRequiresArgument,
|
|
||||||
OptionInvalidArgumentType,
|
|
||||||
ArgumentExtraOperand,
|
|
||||||
ArgumentRequired,
|
|
||||||
ArgumentInvalidType,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Option {
|
|
||||||
char shortName { 0 };
|
|
||||||
const char* longName { nullptr };
|
|
||||||
const char* argumentName { nullptr };
|
|
||||||
const char* usageString { nullptr };
|
|
||||||
const char* manString { nullptr };
|
|
||||||
Required requiresArgument;
|
|
||||||
AcceptFunction acceptValue;
|
|
||||||
|
|
||||||
Error error = Error::None;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Argument {
|
|
||||||
const char* name { nullptr };
|
|
||||||
const char* usageString { nullptr };
|
|
||||||
const char* manString { nullptr };
|
|
||||||
size_t minValues { 0 };
|
|
||||||
size_t maxValues { 1 };
|
|
||||||
size_t addedValues { 0 };
|
|
||||||
AcceptFunction acceptValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool parse(int argc, const char* argv[]);
|
|
||||||
|
|
||||||
void addOption(Option&& option);
|
|
||||||
void addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString);
|
|
||||||
void addOption(const char*& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No);
|
|
||||||
void addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No);
|
|
||||||
void addOption(std::string_view& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No);
|
|
||||||
void addOption(int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No);
|
|
||||||
void addOption(unsigned int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No);
|
|
||||||
void addOption(double& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No);
|
|
||||||
void addOption(std::vector<std::string>& values, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName = "", Required requiresArgument = Required::No);
|
|
||||||
|
|
||||||
void addArgument(Argument&& argument);
|
|
||||||
void addArgument(bool& value, const char* name, const char* usageString, const char* manString, Required required = Required::No);
|
|
||||||
void addArgument(const char*& value, const char* name, const char* usageString, const char* manString, Required required = Required::No);
|
|
||||||
void addArgument(std::string& value, const char* name, const char* usageString, const char* manString, Required required = Required::No);
|
|
||||||
void addArgument(std::string_view& value, const char* name, const char* usageString, const char* manString, Required required = Required::No);
|
|
||||||
void addArgument(int& value, const char* name, const char* usageString, const char* manString, Required required = Required::No);
|
|
||||||
void addArgument(unsigned int& value, const char* name, const char* usageString, const char* manString, Required required = Required::No);
|
|
||||||
void addArgument(double& value, const char* name, const char* usageString, const char* manString, Required required = Required::No);
|
|
||||||
void addArgument(std::vector<std::string>& values, const char* name, const char* usageString, const char* manString, Required required = Required::No);
|
|
||||||
|
|
||||||
void setErrorReporting(bool state) { m_errorReporting = state; }
|
|
||||||
void setExitOnFirstError(bool state) { m_exitOnFirstError = state; }
|
|
||||||
void setStopParsingOnFirstNonOption(bool state) { m_stopParsingOnFirstNonOption = state; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
void printError(char parameter, Error error);
|
|
||||||
void printError(const char* parameter, Error error, bool longName = true);
|
|
||||||
bool parseShortOption(std::string_view option, std::string_view next);
|
|
||||||
bool parseLongOption(std::string_view option, std::string_view next);
|
|
||||||
bool parseArgument(std::string_view argument);
|
|
||||||
|
|
||||||
AcceptFunction getAcceptFunction(bool& value);
|
|
||||||
AcceptFunction getAcceptFunction(const char*& value);
|
|
||||||
AcceptFunction getAcceptFunction(std::string& value);
|
|
||||||
AcceptFunction getAcceptFunction(std::string_view& value);
|
|
||||||
AcceptFunction getAcceptFunction(int& value);
|
|
||||||
AcceptFunction getAcceptFunction(unsigned int& value);
|
|
||||||
AcceptFunction getAcceptFunction(double& value);
|
|
||||||
AcceptFunction getAcceptFunction(std::vector<std::string>& value);
|
|
||||||
|
|
||||||
bool m_errorReporting { true };
|
|
||||||
bool m_exitOnFirstError { true };
|
|
||||||
bool m_stopParsingOnFirstNonOption { false };
|
|
||||||
|
|
||||||
size_t m_optionIndex { 1 };
|
|
||||||
size_t m_argumentIndex { 0 };
|
|
||||||
bool m_nonOptionMode { false };
|
|
||||||
|
|
||||||
const char* m_name { nullptr };
|
|
||||||
std::vector<Option> m_options;
|
|
||||||
std::vector<Argument> m_arguments;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
|
|
||||||
#endif // ARG_PARSER_H
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cassert> // assert
|
|
||||||
#include <cstdint> // int32_t
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream> // ifstream, ios, ofstream
|
|
||||||
#include <memory> // make_unique
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "util/file.h"
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
File::File(const std::string& path)
|
|
||||||
: m_path(path)
|
|
||||||
{
|
|
||||||
// Create input stream object and open file
|
|
||||||
std::ifstream file(path, std::ios::in);
|
|
||||||
assert(file.is_open());
|
|
||||||
|
|
||||||
// Get length of the file
|
|
||||||
file.seekg(0, std::ios::end);
|
|
||||||
int32_t size = file.tellg();
|
|
||||||
file.seekg(0, std::ios::beg);
|
|
||||||
assert(size != -1);
|
|
||||||
|
|
||||||
// Allocate memory filled with zeros
|
|
||||||
auto buffer = std::make_unique<char[]>(size);
|
|
||||||
|
|
||||||
// Fill buffer with file contents
|
|
||||||
file.read(buffer.get(), size);
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
m_data = std::string(buffer.get(), size);
|
|
||||||
}
|
|
||||||
|
|
||||||
File::~File()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
File File::create(const std::string& path)
|
|
||||||
{
|
|
||||||
if (!std::filesystem::exists(path)) {
|
|
||||||
std::ofstream { path };
|
|
||||||
}
|
|
||||||
|
|
||||||
return Util::File(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void File::clear()
|
|
||||||
{
|
|
||||||
m_data.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
File& File::append(const std::string& data)
|
|
||||||
{
|
|
||||||
m_data.append(data);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
File& File::replace(size_t index, size_t length, const std::string& data)
|
|
||||||
{
|
|
||||||
m_data.replace(index, length, data);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
File& File::flush()
|
|
||||||
{
|
|
||||||
// Create output stream object and open file
|
|
||||||
std::ofstream file(m_path, std::ios::out | std::ios::trunc);
|
|
||||||
assert(file.is_open());
|
|
||||||
|
|
||||||
// Write data to disk
|
|
||||||
file.write(m_data.c_str(), m_data.size());
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef FILE_H
|
|
||||||
#define FILE_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
class File {
|
|
||||||
public:
|
|
||||||
File(const std::string& path);
|
|
||||||
virtual ~File();
|
|
||||||
|
|
||||||
static File create(const std::string& path);
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
File& append(const std::string& data);
|
|
||||||
File& replace(size_t index, size_t length, const std::string& data);
|
|
||||||
File& flush();
|
|
||||||
|
|
||||||
const char* c_str() const { return m_data.c_str(); }
|
|
||||||
const std::string& data() const { return m_data; }
|
|
||||||
const std::string& path() const { return m_path; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_path;
|
|
||||||
std::string m_data;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
|
|
||||||
|
|
||||||
#endif // FILE_H
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#include <cstdio> // pclose, perror, popen
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/shell.h"
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
Shell::Shell()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell::Shell(const std::string& output, int status)
|
|
||||||
: m_output(output)
|
|
||||||
, m_status(status)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell Shell::operator()(const char* command)
|
|
||||||
{
|
|
||||||
FILE* shell = popen(command, "r");
|
|
||||||
if (!shell) {
|
|
||||||
perror("\033[31;1mError:\033[0m popen");
|
|
||||||
return { "", -1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string output;
|
|
||||||
|
|
||||||
constexpr int bufferSize = 4096;
|
|
||||||
char buffer[bufferSize];
|
|
||||||
while (fgets(buffer, sizeof(buffer), shell)) {
|
|
||||||
output.append(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
int status = pclose(shell);
|
|
||||||
if (status < 0) {
|
|
||||||
perror("\033[31;1mError:\033[0m pclose");
|
|
||||||
}
|
|
||||||
|
|
||||||
return { output, status };
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell Shell::operator()(std::string command)
|
|
||||||
{
|
|
||||||
return operator()(command.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell Shell::operator()(std::string_view command)
|
|
||||||
{
|
|
||||||
return operator()(command.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#ifndef SHELL_H
|
|
||||||
#define SHELL_H
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
class Shell {
|
|
||||||
public:
|
|
||||||
Shell();
|
|
||||||
virtual ~Shell() {}
|
|
||||||
|
|
||||||
Shell operator()(const char* command);
|
|
||||||
Shell operator()(std::string command);
|
|
||||||
Shell operator()(std::string_view command);
|
|
||||||
|
|
||||||
std::string output() const { return m_output; }
|
|
||||||
int status() const { return m_status; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
Shell(const std::string& output, int status);
|
|
||||||
|
|
||||||
std::string m_output;
|
|
||||||
int m_status { 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
|
|
||||||
#endif // SHELL_H
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#ifndef SINGLETON_H
|
|
||||||
#define SINGLETON_H
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
class Singleton {
|
|
||||||
public:
|
|
||||||
static inline T& the()
|
|
||||||
{
|
|
||||||
if (s_instance == nullptr) {
|
|
||||||
s_instance = new T { s {} };
|
|
||||||
}
|
|
||||||
|
|
||||||
return *s_instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void destroy()
|
|
||||||
{
|
|
||||||
if (s_instance) {
|
|
||||||
delete s_instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
s_instance = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove copy constructor and copy assignment operator
|
|
||||||
Singleton(const Singleton&) = delete;
|
|
||||||
Singleton& operator=(const Singleton&) = delete;
|
|
||||||
Singleton(Singleton&&) = delete;
|
|
||||||
Singleton& operator=(Singleton&&) = delete;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Singleton() {}
|
|
||||||
|
|
||||||
// Constructor token
|
|
||||||
struct s {};
|
|
||||||
|
|
||||||
private:
|
|
||||||
static T* s_instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
T* Singleton<T>::s_instance = nullptr;
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
|
|
||||||
#endif // SINGLETON_H
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
#include <cerrno> // errno, EAGAIN, EINTR
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdio> // perror, ssize_t
|
|
||||||
#include <cstdlib> // exit, WEXITSTATUS
|
|
||||||
#include <cstring> // strcpy, strtok
|
|
||||||
#include <functional> // function
|
|
||||||
#include <sstream> // istringstream
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <sys/wait.h> // waitpid
|
|
||||||
#include <unistd.h> // close, dup2, execvp, fork, pipe, read
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util/system.h"
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
System::System()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
System::System(const std::vector<std::string>& arguments)
|
|
||||||
: m_arguments(arguments)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()()
|
|
||||||
{
|
|
||||||
return exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(const char* command)
|
|
||||||
{
|
|
||||||
return operator()(std::string { command });
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(std::string command)
|
|
||||||
{
|
|
||||||
std::vector<std::string> arguments;
|
|
||||||
|
|
||||||
size_t index = 0;
|
|
||||||
while (index != std::string::npos) {
|
|
||||||
index = command.find_first_of(" ");
|
|
||||||
arguments.push_back(command.substr(0, index));
|
|
||||||
command = command.substr(index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { arguments };
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(std::string_view command)
|
|
||||||
{
|
|
||||||
return operator()(std::string { command });
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(const std::vector<const char*>& arguments)
|
|
||||||
{
|
|
||||||
std::vector<std::string> stringArguments(arguments.size(), "");
|
|
||||||
for (size_t i = 0; i < arguments.size(); ++i) {
|
|
||||||
stringArguments[i] = arguments[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return { stringArguments };
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(const std::vector<std::string>& arguments)
|
|
||||||
{
|
|
||||||
return { arguments };
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(const std::vector<std::string_view>& arguments)
|
|
||||||
{
|
|
||||||
std::vector<std::string> stringArguments(arguments.size(), "");
|
|
||||||
for (size_t i = 0; i < arguments.size(); ++i) {
|
|
||||||
stringArguments[i] = arguments[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return { stringArguments };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shell equivalent ;
|
|
||||||
System System::operator+(System rhs)
|
|
||||||
{
|
|
||||||
auto lhs = *this;
|
|
||||||
|
|
||||||
lhs.exec();
|
|
||||||
rhs.m_output.append(lhs.m_output);
|
|
||||||
rhs.m_error.append(lhs.m_error);
|
|
||||||
rhs.exec();
|
|
||||||
|
|
||||||
return rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator|(System rhs)
|
|
||||||
{
|
|
||||||
auto lhs = *this;
|
|
||||||
|
|
||||||
lhs.exec();
|
|
||||||
rhs.exec(lhs.m_output);
|
|
||||||
|
|
||||||
return rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator&&(System rhs)
|
|
||||||
{
|
|
||||||
auto lhs = *this;
|
|
||||||
|
|
||||||
lhs.exec();
|
|
||||||
if (lhs.m_status > 0) {
|
|
||||||
return lhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
rhs.m_output.append(lhs.m_output);
|
|
||||||
rhs.m_error.append(lhs.m_error);
|
|
||||||
rhs.exec();
|
|
||||||
|
|
||||||
return rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator||(System rhs)
|
|
||||||
{
|
|
||||||
auto lhs = *this;
|
|
||||||
|
|
||||||
lhs.exec();
|
|
||||||
if (lhs.m_status == 0) {
|
|
||||||
return lhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
rhs.m_output.append(lhs.m_output);
|
|
||||||
rhs.m_error.append(lhs.m_error);
|
|
||||||
rhs.exec();
|
|
||||||
|
|
||||||
return rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cut -f -d
|
|
||||||
System& System::cut(uint32_t field, char delimiter)
|
|
||||||
{
|
|
||||||
exec();
|
|
||||||
|
|
||||||
return apply([&field, &delimiter](std::vector<std::string>& lines) {
|
|
||||||
for (auto& line : lines) {
|
|
||||||
size_t count = 1;
|
|
||||||
size_t index = 0;
|
|
||||||
while (index != std::string::npos) {
|
|
||||||
if (count == field) {
|
|
||||||
line = line.substr(0, line.find_first_of(delimiter));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = line.find_first_of(delimiter);
|
|
||||||
line = line.substr(index + 1);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
System& System::sort(bool unique)
|
|
||||||
{
|
|
||||||
exec();
|
|
||||||
|
|
||||||
return apply([&unique](std::vector<std::string>& lines) {
|
|
||||||
std::sort(lines.begin(), lines.end());
|
|
||||||
|
|
||||||
if (unique) {
|
|
||||||
auto last = std::unique(lines.begin(), lines.end());
|
|
||||||
lines.erase(last, lines.end());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// tail -n
|
|
||||||
System& System::tail(int32_t number, bool starting)
|
|
||||||
{
|
|
||||||
exec();
|
|
||||||
|
|
||||||
return apply([&number, &starting](std::vector<std::string>& lines) {
|
|
||||||
number = abs(number);
|
|
||||||
if (!starting) {
|
|
||||||
lines.erase(lines.begin(), lines.end() - number);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lines.erase(lines.begin(), lines.begin() + number - 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
System& System::apply(LineCallback callback)
|
|
||||||
{
|
|
||||||
exec();
|
|
||||||
|
|
||||||
std::vector<std::string> lines;
|
|
||||||
|
|
||||||
auto stream = std::istringstream(m_output);
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(stream, line)) {
|
|
||||||
lines.push_back(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(lines);
|
|
||||||
|
|
||||||
m_output.clear();
|
|
||||||
for (size_t i = 0; i < lines.size(); ++i) {
|
|
||||||
m_output.append(lines.at(i) + '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void System::print(const std::vector<std::string>& arguments)
|
|
||||||
{
|
|
||||||
if (!arguments.size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("----------\n");
|
|
||||||
printf("size: %zu\n", arguments.size());
|
|
||||||
printf("command: ");
|
|
||||||
for (size_t i = 0; i < arguments.size(); ++i) {
|
|
||||||
printf("%s ", arguments.at(i).c_str());
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
printf("----------\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
System System::exec(std::string input)
|
|
||||||
{
|
|
||||||
if (m_arguments.empty()) {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
int stdinFd[2];
|
|
||||||
int stdoutFd[2];
|
|
||||||
int stderrFd[2];
|
|
||||||
if (pipe(stdinFd) < 0) {
|
|
||||||
perror("\033[31;1mError:\033[0m pipe");
|
|
||||||
}
|
|
||||||
if (pipe(stdoutFd) < 0) {
|
|
||||||
perror("\033[31;1mError:\033[0m pipe");
|
|
||||||
}
|
|
||||||
if (pipe(stderrFd) < 0) {
|
|
||||||
perror("\033[31;1mError:\033[0m pipe");
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t pid = fork();
|
|
||||||
switch (pid) {
|
|
||||||
// Failed
|
|
||||||
case -1:
|
|
||||||
perror("\033[31;1mError:\033[0m fork");
|
|
||||||
break;
|
|
||||||
// Child
|
|
||||||
case 0: {
|
|
||||||
close(stdinFd[WriteFileDescriptor]);
|
|
||||||
dup2(stdinFd[ReadFileDescriptor], fileno(stdin));
|
|
||||||
close(stdinFd[ReadFileDescriptor]);
|
|
||||||
|
|
||||||
close(stdoutFd[ReadFileDescriptor]);
|
|
||||||
dup2(stdoutFd[WriteFileDescriptor], fileno(stdout));
|
|
||||||
close(stdoutFd[WriteFileDescriptor]);
|
|
||||||
|
|
||||||
close(stderrFd[ReadFileDescriptor]);
|
|
||||||
dup2(stderrFd[WriteFileDescriptor], fileno(stderr));
|
|
||||||
close(stderrFd[WriteFileDescriptor]);
|
|
||||||
|
|
||||||
std::vector<char*> charArguments(m_arguments.size() + 1, 0);
|
|
||||||
for (size_t i = 0; i < m_arguments.size(); ++i) {
|
|
||||||
charArguments[i] = const_cast<char*>(m_arguments[i].c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
execvp(charArguments[0], &charArguments[0]);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
// Parent
|
|
||||||
default:
|
|
||||||
m_arguments.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
close(stdinFd[ReadFileDescriptor]);
|
|
||||||
if (!input.empty()) {
|
|
||||||
write(stdinFd[WriteFileDescriptor], input.c_str(), input.size());
|
|
||||||
}
|
|
||||||
close(stdinFd[WriteFileDescriptor]);
|
|
||||||
|
|
||||||
readFromFileDescriptor(stdoutFd, m_output);
|
|
||||||
readFromFileDescriptor(stderrFd, m_error);
|
|
||||||
|
|
||||||
int result;
|
|
||||||
do {
|
|
||||||
result = waitpid(pid, &m_status, 0);
|
|
||||||
} while (result == -1 && errno == EINTR);
|
|
||||||
m_status = WEXITSTATUS(m_status);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void System::readFromFileDescriptor(int fileDescriptor[2], std::string& output)
|
|
||||||
{
|
|
||||||
close(fileDescriptor[WriteFileDescriptor]);
|
|
||||||
|
|
||||||
constexpr int bufferSize = 4096;
|
|
||||||
char buffer[bufferSize];
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
const ssize_t result = read(fileDescriptor[ReadFileDescriptor], buffer, bufferSize);
|
|
||||||
if (result > 0) {
|
|
||||||
output.append(buffer, result);
|
|
||||||
}
|
|
||||||
// EOF
|
|
||||||
if (result == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Error
|
|
||||||
else if (result == -1) {
|
|
||||||
if (errno != EAGAIN && errno != EINTR) {
|
|
||||||
perror("\033[31;1mError:\033[0m read");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(fileDescriptor[ReadFileDescriptor]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
#ifndef SYSTEM_H
|
|
||||||
#define SYSTEM_H
|
|
||||||
|
|
||||||
#include <cstdint> // int32_t, uint32_t
|
|
||||||
#include <functional> // function
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
using LineCallback = std::function<void(std::vector<std::string>&)>;
|
|
||||||
|
|
||||||
class System {
|
|
||||||
public:
|
|
||||||
System();
|
|
||||||
virtual ~System() {}
|
|
||||||
|
|
||||||
enum FileDescriptor {
|
|
||||||
ReadFileDescriptor,
|
|
||||||
WriteFileDescriptor,
|
|
||||||
};
|
|
||||||
|
|
||||||
System operator()();
|
|
||||||
System operator()(const char* command);
|
|
||||||
System operator()(std::string command);
|
|
||||||
System operator()(std::string_view command);
|
|
||||||
System operator()(const std::vector<const char*>& arguments);
|
|
||||||
System operator()(const std::vector<std::string>& arguments);
|
|
||||||
System operator()(const std::vector<std::string_view>& arguments);
|
|
||||||
|
|
||||||
// Operator order
|
|
||||||
// + -> | -> && -> ||
|
|
||||||
System operator+(System rhs);
|
|
||||||
System operator|(System rhs);
|
|
||||||
System operator&&(System rhs);
|
|
||||||
System operator||(System rhs);
|
|
||||||
|
|
||||||
System& cut(uint32_t field, char delimiter = '\t');
|
|
||||||
System& sort(bool unique = false);
|
|
||||||
System& tail(int32_t number, bool starting = false);
|
|
||||||
System& apply(LineCallback callback);
|
|
||||||
|
|
||||||
void print(const std::vector<std::string>& arguments);
|
|
||||||
|
|
||||||
const std::vector<std::string>& arguments() const { return m_arguments; }
|
|
||||||
std::string output() const { return m_output; }
|
|
||||||
std::string error() const { return m_error; }
|
|
||||||
int status() const { return m_status; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
System(const std::vector<std::string>& arguments);
|
|
||||||
|
|
||||||
System exec(std::string input = "");
|
|
||||||
void readFromFileDescriptor(int fileDescriptor[2], std::string& output);
|
|
||||||
|
|
||||||
std::vector<std::string> m_arguments;
|
|
||||||
std::string m_output;
|
|
||||||
std::string m_error;
|
|
||||||
int m_status { 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
|
|
||||||
#endif // SYSTEM_H
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
#include <chrono> // high_resolution_clock, seconds, milliseconds, microseconds, nanoseconds
|
|
||||||
#include <cstdint> // uint64_t
|
|
||||||
#include <cstdio> // printf
|
|
||||||
|
|
||||||
#include "util/timer.h"
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
Timer::Timer()
|
|
||||||
: m_start(std::chrono::high_resolution_clock::now())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer Timer::operator-(const Timer& timer)
|
|
||||||
{
|
|
||||||
return Timer(TimePoint { m_start - timer.start() });
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename To, typename From>
|
|
||||||
To Timer::to(From from)
|
|
||||||
{
|
|
||||||
return std::chrono::duration_cast<To>(from);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::toSeconds()
|
|
||||||
{
|
|
||||||
return to<std::chrono::seconds>(m_start.time_since_epoch()).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::toMilliseconds()
|
|
||||||
{
|
|
||||||
return to<std::chrono::milliseconds>(m_start.time_since_epoch()).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::toMicroseconds()
|
|
||||||
{
|
|
||||||
return to<std::chrono::microseconds>(m_start.time_since_epoch()).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::toNanoseconds()
|
|
||||||
{
|
|
||||||
return to<std::chrono::nanoseconds>(m_start.time_since_epoch()).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
uint64_t Timer::elapsed()
|
|
||||||
{
|
|
||||||
auto now = std::chrono::high_resolution_clock::now();
|
|
||||||
return std::chrono::duration_cast<T>(now - m_start).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::elapsedSeconds()
|
|
||||||
{
|
|
||||||
return elapsed<std::chrono::seconds>();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::elapsedMilliseconds()
|
|
||||||
{
|
|
||||||
return elapsed<std::chrono::milliseconds>();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::elapsedMicroseconds()
|
|
||||||
{
|
|
||||||
return elapsed<std::chrono::microseconds>();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::elapsedNanoseconds()
|
|
||||||
{
|
|
||||||
return elapsed<std::chrono::nanoseconds>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Timer::fancyPrint(uint64_t nanoseconds)
|
|
||||||
{
|
|
||||||
if (nanoseconds > 999999999) {
|
|
||||||
printf("%.3fs", nanoseconds / 1000000000.0);
|
|
||||||
}
|
|
||||||
else if (nanoseconds > 999999) {
|
|
||||||
printf("%.0fms", nanoseconds / 1000000.0);
|
|
||||||
}
|
|
||||||
else if (nanoseconds > 999) {
|
|
||||||
printf("%.0fμs", nanoseconds / 1000.0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf("%luns", nanoseconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#ifndef TIMER_H
|
|
||||||
#define TIMER_H
|
|
||||||
|
|
||||||
#include <chrono> // high_resolution_clock
|
|
||||||
#include <cstdint> // uint64_t
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
using TimePoint = std::chrono::high_resolution_clock::time_point;
|
|
||||||
|
|
||||||
class Timer {
|
|
||||||
public:
|
|
||||||
Timer();
|
|
||||||
Timer(const TimePoint& timePoint)
|
|
||||||
: m_start(timePoint)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer operator-(const Timer& timer);
|
|
||||||
|
|
||||||
template<typename To, typename From>
|
|
||||||
To to(From from);
|
|
||||||
uint64_t toSeconds();
|
|
||||||
uint64_t toMilliseconds();
|
|
||||||
uint64_t toMicroseconds();
|
|
||||||
uint64_t toNanoseconds();
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
uint64_t elapsed();
|
|
||||||
uint64_t elapsedSeconds();
|
|
||||||
uint64_t elapsedMilliseconds();
|
|
||||||
uint64_t elapsedMicroseconds();
|
|
||||||
uint64_t elapsedNanoseconds();
|
|
||||||
|
|
||||||
static void fancyPrint(uint64_t nanoseconds);
|
|
||||||
|
|
||||||
const TimePoint& start() const { return m_start; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
TimePoint m_start;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
|
|
||||||
#endif // TIMER_H
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#ifndef TEST_H
|
|
||||||
#define TEST_H
|
|
||||||
|
|
||||||
#include <cstdio> // fprintf
|
|
||||||
#include <iostream> // cerr
|
|
||||||
|
|
||||||
#define VERIFY(x, result) \
|
|
||||||
if (!(x)) { \
|
|
||||||
fprintf(stderr, " \033[31;1mFAIL:\033[0m %s:%d: VERIFY(%s) failed\n", \
|
|
||||||
__FILE__, __LINE__, #x); \
|
|
||||||
Test::TestSuite::the().currentTestCaseFailed(); \
|
|
||||||
result; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define EXPECT(x) \
|
|
||||||
if (!(x)) { \
|
|
||||||
fprintf(stderr, " \033[31;1mFAIL:\033[0m %s:%d: EXPECT(%s) failed\n", \
|
|
||||||
__FILE__, __LINE__, #x); \
|
|
||||||
Test::TestSuite::the().currentTestCaseFailed(); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define EXPECT_EQ(a, b) \
|
|
||||||
if (a != b) { \
|
|
||||||
std::cerr << " \033[31;1mFAIL:\033[0m " << __FILE__ << ":" << __LINE__ \
|
|
||||||
<< ": EXPECT_EQ(" << #a << ", " << #b ") failed with" \
|
|
||||||
<< " lhs='" << a << "' and rhs='" << b << "'" << std::endl; \
|
|
||||||
Test::TestSuite::the().currentTestCaseFailed(); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define EXPECT_NE(a, b) \
|
|
||||||
if (a == b) { \
|
|
||||||
std::cerr << " \033[31;1mFAIL:\033[0m " << __FILE__ << ":" << __LINE__ \
|
|
||||||
<< ": EXPECT_NE(" << #a << ", " << #b ") failed with" \
|
|
||||||
<< " lhs='" << a << "' and rhs='" << b << "'" << std::endl; \
|
|
||||||
Test::TestSuite::the().currentTestCaseFailed(); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // TEST_H
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#include "testsuite.h"
|
|
||||||
|
|
||||||
int main(int, const char*[])
|
|
||||||
{
|
|
||||||
Test::TestSuite::the().run();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#ifndef TEST_CASE_H
|
|
||||||
#define TEST_CASE_H
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#define __TEST_CASE_FUNCTION(x) __test##x
|
|
||||||
#define __TEST_CASE_STRUCT(x) __testStruct##x
|
|
||||||
|
|
||||||
#define TEST_CASE(x) \
|
|
||||||
static void __TEST_CASE_FUNCTION(x)(); \
|
|
||||||
struct __TEST_CASE_STRUCT(x) { \
|
|
||||||
__TEST_CASE_STRUCT(x) \
|
|
||||||
() \
|
|
||||||
{ \
|
|
||||||
Test::TestSuite::the().addCase( \
|
|
||||||
{ #x, __TEST_CASE_FUNCTION(x) }); \
|
|
||||||
} \
|
|
||||||
}; \
|
|
||||||
static struct __TEST_CASE_STRUCT(x) __TEST_CASE_STRUCT(x); \
|
|
||||||
static void __TEST_CASE_FUNCTION(x)()
|
|
||||||
|
|
||||||
namespace Test {
|
|
||||||
|
|
||||||
using TestFunction = std::function<void()>;
|
|
||||||
|
|
||||||
class TestCase {
|
|
||||||
public:
|
|
||||||
TestCase(const char* name, TestFunction&& function)
|
|
||||||
: m_name(name)
|
|
||||||
, m_function(function)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* name() const { return m_name; }
|
|
||||||
const TestFunction& function() const { return m_function; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
const char* m_name { nullptr };
|
|
||||||
TestFunction m_function;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Test
|
|
||||||
|
|
||||||
#endif // TEST_CASE_H
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdio> // fclose, fopen, printf, stdout
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/timer.h"
|
|
||||||
|
|
||||||
namespace Test {
|
|
||||||
|
|
||||||
TestSuite::TestSuite(s)
|
|
||||||
{
|
|
||||||
m_outputStd = stdout;
|
|
||||||
m_outputErr = stderr;
|
|
||||||
m_outputNull = fopen("/dev/null", "w"); // Windows: nul
|
|
||||||
}
|
|
||||||
|
|
||||||
TestSuite::~TestSuite()
|
|
||||||
{
|
|
||||||
fclose(m_outputNull);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestSuite::run()
|
|
||||||
{
|
|
||||||
const char* escapePass = "\033[42;30;1m";
|
|
||||||
const char* escapeFail = "\033[41;1m";
|
|
||||||
const char* escapeGreen = "\033[32m";
|
|
||||||
const char* escapeGrey = "\033[37m";
|
|
||||||
const char* escapeRed = "\033[31m";
|
|
||||||
const char* escapeReset = "\033[0m";
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
printf("---- Running %zu Test Cases ----\n", m_cases.size());
|
|
||||||
|
|
||||||
Util::Timer totalTimer;
|
|
||||||
|
|
||||||
size_t caseFailedCount = 0;
|
|
||||||
for (size_t i = 0; i < m_cases.size(); ++i) {
|
|
||||||
|
|
||||||
printf(" START %s (%zu/%zu)\n", m_cases.at(i).name(), i + 1, m_cases.size());
|
|
||||||
m_currentTestCasePassed = true;
|
|
||||||
|
|
||||||
Util::Timer caseTimer;
|
|
||||||
m_cases.at(i).function()();
|
|
||||||
double elapsed = caseTimer.elapsedNanoseconds();
|
|
||||||
|
|
||||||
std::string state;
|
|
||||||
if (m_currentTestCasePassed) {
|
|
||||||
state.append(escapePass);
|
|
||||||
state.append(" PASS ");
|
|
||||||
state.append(escapeReset);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
caseFailedCount++;
|
|
||||||
state.append(escapeFail);
|
|
||||||
state.append(" FAIL ");
|
|
||||||
state.append(escapeReset);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("%s %s %s(", state.c_str(), m_cases.at(i).name(), escapeGrey);
|
|
||||||
Util::Timer::fancyPrint(elapsed);
|
|
||||||
printf(")%s\n", escapeReset);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
printf("Tests: %s%zu failed%s, %s%zu passed%s, %zu total\n",
|
|
||||||
escapeRed, caseFailedCount, escapeReset,
|
|
||||||
escapeGreen, m_cases.size() - caseFailedCount, escapeReset,
|
|
||||||
m_cases.size());
|
|
||||||
|
|
||||||
printf("Time: ");
|
|
||||||
Util::Timer::fancyPrint(totalTimer.elapsedNanoseconds());
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Test
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#ifndef TEST_SUITE_H
|
|
||||||
#define TEST_SUITE_H
|
|
||||||
|
|
||||||
#include <cstdio> // FILE
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "util/singleton.h"
|
|
||||||
|
|
||||||
namespace Test {
|
|
||||||
|
|
||||||
class TestSuite final : public Util::Singleton<TestSuite> {
|
|
||||||
public:
|
|
||||||
TestSuite(s);
|
|
||||||
virtual ~TestSuite();
|
|
||||||
|
|
||||||
void run();
|
|
||||||
void addCase(const TestCase& testCase) { m_cases.push_back(testCase); }
|
|
||||||
void currentTestCaseFailed() { m_currentTestCasePassed = false; }
|
|
||||||
|
|
||||||
FILE* outputStd() const { return m_outputStd; }
|
|
||||||
FILE* outputErr() const { return m_outputErr; }
|
|
||||||
FILE* outputNull() const { return m_outputNull; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool m_currentTestCasePassed { true };
|
|
||||||
FILE* m_outputStd { nullptr };
|
|
||||||
FILE* m_outputErr { nullptr };
|
|
||||||
FILE* m_outputNull { nullptr };
|
|
||||||
|
|
||||||
std::vector<TestCase> m_cases;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Test
|
|
||||||
|
|
||||||
#endif // TEST_SUITE_H
|
|
||||||
+389
-43
@@ -1,25 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
|
||||||
#include <cstdio> // stderr
|
#include <cstdio> // stderr
|
||||||
#include <filesystem> // path
|
#include <filesystem> // path
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unistd.h> // geteuid, setegid, seteuid
|
#include <unistd.h> // geteuid, setegid, seteuid
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ruc/file.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "dotfile.h"
|
#include "dotfile.h"
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
#include "macro.h"
|
#include "macro.h"
|
||||||
#include "testcase.h"
|
#include "testcase.h"
|
||||||
#include "testsuite.h"
|
#include "testsuite.h"
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/system.h"
|
|
||||||
|
|
||||||
const bool root = !geteuid() ? true : false;
|
const bool root = !geteuid() ? true : false;
|
||||||
const std::filesystem::path homeDirectory = "/home/" + Machine::the().username();
|
const std::filesystem::path homeDirectory = "/home/" + Machine::the().username();
|
||||||
@@ -27,7 +27,7 @@ const size_t homeDirectorySize = homeDirectory.string().size();
|
|||||||
|
|
||||||
void createTestDotfiles(const std::vector<std::string>& fileNames, const std::vector<std::string>& fileContents, bool asRoot = false)
|
void createTestDotfiles(const std::vector<std::string>& fileNames, const std::vector<std::string>& fileContents, bool asRoot = false)
|
||||||
{
|
{
|
||||||
VERIFY(fileNames.size() == fileContents.size(), return);
|
EXPECT(fileNames.size() == fileContents.size(), return);
|
||||||
|
|
||||||
if (root && !asRoot) {
|
if (root && !asRoot) {
|
||||||
setegid(Machine::the().gid());
|
setegid(Machine::the().gid());
|
||||||
@@ -42,7 +42,7 @@ void createTestDotfiles(const std::vector<std::string>& fileNames, const std::ve
|
|||||||
std::filesystem::create_directories(directory);
|
std::filesystem::create_directories(directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto file = Util::File::create(fileName);
|
auto file = ruc::File::create(fileName);
|
||||||
if (file.data().size() == 0) {
|
if (file.data().size() == 0) {
|
||||||
file.append(fileContents.at(i).c_str()).flush();
|
file.append(fileContents.at(i).c_str()).flush();
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ void createTestDotfiles(const std::vector<std::string>& fileNames, const std::ve
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeTestDotfiles(const std::vector<std::string>& files)
|
void removeTestDotfiles(const std::vector<std::string>& files, bool deleteInHome = true)
|
||||||
{
|
{
|
||||||
for (auto file : files) {
|
for (auto file : files) {
|
||||||
// Get the top-level directory
|
// Get the top-level directory
|
||||||
@@ -68,6 +68,10 @@ void removeTestDotfiles(const std::vector<std::string>& files)
|
|||||||
std::filesystem::remove_all(file);
|
std::filesystem::remove_all(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!deleteInHome) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Delete recursively in home directory
|
// Delete recursively in home directory
|
||||||
file = homeDirectory / file;
|
file = homeDirectory / file;
|
||||||
if (std::filesystem::exists(file) || std::filesystem::is_symlink(file)) {
|
if (std::filesystem::exists(file) || std::filesystem::is_symlink(file)) {
|
||||||
@@ -76,8 +80,274 @@ void removeTestDotfiles(const std::vector<std::string>& files)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testDotfileFilters(const std::unordered_map<std::string, bool>& tests,
|
||||||
|
const std::vector<std::string>& testIgnorePatterns)
|
||||||
|
{
|
||||||
|
std::vector<std::string> fileNames;
|
||||||
|
std::vector<std::string> fileContents;
|
||||||
|
for (const auto& test : tests) {
|
||||||
|
fileNames.push_back(test.first);
|
||||||
|
fileContents.push_back("");
|
||||||
|
}
|
||||||
|
|
||||||
|
createTestDotfiles(fileNames, fileContents);
|
||||||
|
|
||||||
|
for (const auto& path : fileNames) {
|
||||||
|
bool result = Dotfile::the().match("/" + path, testIgnorePatterns);
|
||||||
|
EXPECT_EQ(result, tests.at(path), printf(" path = '%s'\n", path.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTestDotfiles(fileNames, false);
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesLiteralIgnoreAllFiles)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "access.log", true },
|
||||||
|
{ "logs/access.log", true },
|
||||||
|
{ "var/logs/access.log", true },
|
||||||
|
{ "error.log", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"access.log",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesLiteralIgnoreFileInRoot)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "access.log", true },
|
||||||
|
{ "logs/access.log", false },
|
||||||
|
{ "var/logs/access.log", false },
|
||||||
|
{ "error.log", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"/access.log",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesLiteralIgnoreDirectories)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "build/executable", true },
|
||||||
|
{ "doc/build", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"build/",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllFilesWithExtension)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "error.log", true },
|
||||||
|
{ "logs/debug.log", true },
|
||||||
|
{ "var/logs/error.log", true },
|
||||||
|
{ ".log/output.txt", true },
|
||||||
|
{ "program.log/output.txt", true },
|
||||||
|
{ "log.txt", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"*.log",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllFilesInRootWithExtension)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "error.log", true },
|
||||||
|
{ "logs/debug.log", false },
|
||||||
|
{ "var/logs/error.log", false },
|
||||||
|
{ ".log/output.txt", true },
|
||||||
|
{ "program.log/output.txt", true },
|
||||||
|
{ "log.txt", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"/*.log",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreFileWithAllExtensions)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "README.md", true },
|
||||||
|
{ "doc/README.org", true },
|
||||||
|
{ "doc/compressed/README.tar.gz", true },
|
||||||
|
{ "doc/compressed/BIG_README.tar.gz", false }, // FIXME
|
||||||
|
{ "Documentation.README", false },
|
||||||
|
{ "Config.org", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"README.*",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllWithStartingPattern)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "cmake/uninstall.cmake.in", true },
|
||||||
|
{ "cmake/templates/template.cmake.in", true },
|
||||||
|
{ "build/cmake/executable", true },
|
||||||
|
{ "build/cmakefiles/makefile", true },
|
||||||
|
{ "CMakeLists.txt", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"cmake*",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllInRootWithStartingPattern)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "project-directory/README.org", true },
|
||||||
|
{ "project-directory/build/executable", true },
|
||||||
|
{ "project-file", true },
|
||||||
|
{ "doc/project-instructions.txt", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"/project*",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllInDirectory)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "build/x32/executable", true },
|
||||||
|
{ "build/x64/executable", true },
|
||||||
|
{ "build/executable", true },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"build/*",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreFileInSubDirectory)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "build/x32/executable", true },
|
||||||
|
{ "build/x64/executable", true },
|
||||||
|
{ "build/x64/config.json", false },
|
||||||
|
{ "project/build/x64/config.json", false },
|
||||||
|
{ "build/executable", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"build/*/executable",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllInSubDirectory)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "build/x32/executable", true },
|
||||||
|
{ "build/x64/executable", true },
|
||||||
|
{ "build/x64/config.json", true },
|
||||||
|
{ "project/build/x64/config.json", true },
|
||||||
|
{ "build/executable", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"build/*/*",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllDirectoriesWithStartingPattern)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "include/header.h", true },
|
||||||
|
{ "include-dependency/header.h", true },
|
||||||
|
{ "include-file", false },
|
||||||
|
{ "src/include/header.h", true },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"include*/",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllDirectoriesInRootWithStartingPattern)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "include/header.h", true },
|
||||||
|
{ "include-dependency/header.h", true },
|
||||||
|
{ "include-file", false },
|
||||||
|
{ "src/include/header.h", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"/include*/",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllDirectoriesWithEndingPattern)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "include/header.h", true },
|
||||||
|
{ "dependency-include/header.h", true },
|
||||||
|
{ "file-include", false },
|
||||||
|
{ "src/include/header.h", true },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"*include/",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE(DotfilesWildcardIgnoreAllDirectoriesInRootWithEndingPattern)
|
||||||
|
{
|
||||||
|
std::unordered_map<std::string, bool> tests = {
|
||||||
|
{ "include/header.h", true },
|
||||||
|
{ "dependency-include/header.h", true },
|
||||||
|
{ "file-include", false },
|
||||||
|
{ "src/include/header.h", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<std::string> testFilters = {
|
||||||
|
"/*include/",
|
||||||
|
};
|
||||||
|
|
||||||
|
testDotfileFilters(tests, testFilters);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE(AddDotfiles)
|
TEST_CASE(AddDotfiles)
|
||||||
{
|
{
|
||||||
std::vector<std::string> fileNames = {
|
std::vector<std::string> fileNames = {
|
||||||
@@ -100,11 +370,11 @@ TEST_CASE(AddDotfiles)
|
|||||||
Dotfile::the().add(fileNames);
|
Dotfile::the().add(fileNames);
|
||||||
|
|
||||||
for (const auto& file : fileNames) {
|
for (const auto& file : fileNames) {
|
||||||
VERIFY(std::filesystem::exists(file), continue);
|
EXPECT(std::filesystem::exists(file), continue);
|
||||||
VERIFY(std::filesystem::exists(file.substr(homeDirectorySize + 1)), continue);
|
EXPECT(std::filesystem::exists(file.substr(homeDirectorySize + 1)), continue);
|
||||||
|
|
||||||
Util::File lhs(file);
|
ruc::File lhs(file);
|
||||||
Util::File rhs(file.substr(homeDirectorySize + 1));
|
ruc::File rhs(file.substr(homeDirectorySize + 1));
|
||||||
EXPECT_EQ(lhs.data(), rhs.data());
|
EXPECT_EQ(lhs.data(), rhs.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,9 +383,9 @@ TEST_CASE(AddDotfiles)
|
|||||||
|
|
||||||
TEST_CASE(AddNonExistentDotfiles)
|
TEST_CASE(AddNonExistentDotfiles)
|
||||||
{
|
{
|
||||||
stderr = Test::TestSuite::the().outputNull();
|
stderr = test::TestSuite::the().outputNull();
|
||||||
Dotfile::the().add({ homeDirectory / "__non-existent-test-file" });
|
Dotfile::the().add({ homeDirectory / "__non-existent-test-file" });
|
||||||
stderr = Test::TestSuite::the().outputErr();
|
stderr = test::TestSuite::the().outputErr();
|
||||||
|
|
||||||
EXPECT(!std::filesystem::exists("__non-existent-test-file"));
|
EXPECT(!std::filesystem::exists("__non-existent-test-file"));
|
||||||
removeTestDotfiles({ "__non-existent-test-file" });
|
removeTestDotfiles({ "__non-existent-test-file" });
|
||||||
@@ -156,11 +426,11 @@ TEST_CASE(PullDotfiles)
|
|||||||
Dotfile::the().pull(fileNames);
|
Dotfile::the().pull(fileNames);
|
||||||
|
|
||||||
for (size_t i = 0; i < fileNames.size(); ++i) {
|
for (size_t i = 0; i < fileNames.size(); ++i) {
|
||||||
VERIFY(std::filesystem::exists(homeFileNames.at(i)), continue);
|
EXPECT(std::filesystem::exists(homeFileNames.at(i)), continue);
|
||||||
VERIFY(std::filesystem::exists(fileNames.at(i)), continue);
|
EXPECT(std::filesystem::exists(fileNames.at(i)), continue);
|
||||||
|
|
||||||
Util::File lhs(homeFileNames.at(i));
|
ruc::File lhs(homeFileNames.at(i));
|
||||||
Util::File rhs(fileNames.at(i));
|
ruc::File rhs(fileNames.at(i));
|
||||||
EXPECT_EQ(lhs.data(), rhs.data());
|
EXPECT_EQ(lhs.data(), rhs.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,18 +459,18 @@ TEST_CASE(PushDotfiles)
|
|||||||
Dotfile::the().push(fileNames);
|
Dotfile::the().push(fileNames);
|
||||||
|
|
||||||
for (const auto& file : fileNames) {
|
for (const auto& file : fileNames) {
|
||||||
VERIFY(std::filesystem::exists(file), continue);
|
EXPECT(std::filesystem::exists(file), continue);
|
||||||
VERIFY(std::filesystem::exists(homeDirectory / file), continue);
|
EXPECT(std::filesystem::exists(homeDirectory / file), continue);
|
||||||
|
|
||||||
Util::File lhs(file);
|
ruc::File lhs(file);
|
||||||
Util::File rhs(homeDirectory / file);
|
ruc::File rhs((homeDirectory / file).string());
|
||||||
EXPECT_EQ(lhs.data(), rhs.data());
|
EXPECT_EQ(lhs.data(), rhs.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
removeTestDotfiles(fileNames);
|
removeTestDotfiles(fileNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE(PushDotfilesWithExcludePath)
|
TEST_CASE(PushDotfilesWithIgnorePattern)
|
||||||
{
|
{
|
||||||
std::vector<std::string> fileNames = {
|
std::vector<std::string> fileNames = {
|
||||||
"__test-file-1",
|
"__test-file-1",
|
||||||
@@ -211,13 +481,16 @@ TEST_CASE(PushDotfilesWithExcludePath)
|
|||||||
|
|
||||||
createTestDotfiles(fileNames, { "", "", "", "" });
|
createTestDotfiles(fileNames, { "", "", "", "" });
|
||||||
|
|
||||||
Config::the().setExcludePaths({
|
auto ignorePatterns = Config::the().ignorePatterns();
|
||||||
{ "__test-file-1", "file" },
|
Config::the().setIgnorePatterns({
|
||||||
{ "__subdir", "directory" },
|
"__test-file-1",
|
||||||
{ ".test", "endsWith" },
|
"__subdir/",
|
||||||
|
"*.test",
|
||||||
});
|
});
|
||||||
|
|
||||||
Dotfile::the().push(fileNames);
|
Dotfile::the().push(fileNames);
|
||||||
Config::the().setExcludePaths({});
|
|
||||||
|
Config::the().setIgnorePatterns(ignorePatterns);
|
||||||
|
|
||||||
for (const auto& file : fileNames) {
|
for (const auto& file : fileNames) {
|
||||||
EXPECT(!std::filesystem::exists(homeDirectory / file));
|
EXPECT(!std::filesystem::exists(homeDirectory / file));
|
||||||
@@ -229,8 +502,8 @@ TEST_CASE(PushDotfilesWithExcludePath)
|
|||||||
TEST_CASE(PushDotfilesSelectivelyComment)
|
TEST_CASE(PushDotfilesSelectivelyComment)
|
||||||
{
|
{
|
||||||
std::vector<std::string> fileNames;
|
std::vector<std::string> fileNames;
|
||||||
for (size_t i = 0; i < 36; ++i) {
|
for (size_t i = 0; i < 44; ++i) {
|
||||||
fileNames.push_back( "__test-file-" + std::to_string(i + 1));
|
fileNames.push_back("__test-file-" + std::to_string(i + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto distro = Machine::the().distroId();
|
auto distro = Machine::the().distroId();
|
||||||
@@ -404,6 +677,43 @@ test data /**/ comment
|
|||||||
/* test data /**/ uncomment */
|
/* test data /**/ uncomment */
|
||||||
/* <<< */
|
/* <<< */
|
||||||
)",
|
)",
|
||||||
|
|
||||||
|
// Comment <!---->
|
||||||
|
"<!-- >>> distro=" + placeholder + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> comment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + placeholder + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> comment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + placeholder + " -->" + R"(
|
||||||
|
test data <!----> comment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + placeholder + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> comment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
|
||||||
|
// Uncomment <!---->
|
||||||
|
"<!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> uncomment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro="
|
||||||
|
+ distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> uncomment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> uncomment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> uncomment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::string> pushedFileContents = {
|
std::vector<std::string> pushedFileContents = {
|
||||||
@@ -572,6 +882,42 @@ test data /**/ uncomment
|
|||||||
test data /**/ uncomment
|
test data /**/ uncomment
|
||||||
/* <<< */
|
/* <<< */
|
||||||
)",
|
)",
|
||||||
|
|
||||||
|
// Comment <!---->
|
||||||
|
"<!-- >>> distro=" + placeholder + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> comment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + placeholder + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> comment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + placeholder + " -->" + R"(
|
||||||
|
<!-- test data <!----> comment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + placeholder + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> comment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
|
||||||
|
// Uncomment <!---->
|
||||||
|
"<!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> uncomment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> uncomment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> uncomment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> uncomment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
};
|
};
|
||||||
|
|
||||||
createTestDotfiles(fileNames, fileContents);
|
createTestDotfiles(fileNames, fileContents);
|
||||||
@@ -580,10 +926,10 @@ test data /**/ uncomment
|
|||||||
|
|
||||||
for (size_t i = 0; i < fileNames.size(); ++i) {
|
for (size_t i = 0; i < fileNames.size(); ++i) {
|
||||||
const auto& file = fileNames.at(i);
|
const auto& file = fileNames.at(i);
|
||||||
VERIFY(std::filesystem::exists(file), continue);
|
EXPECT(std::filesystem::exists(file), continue);
|
||||||
VERIFY(std::filesystem::exists(homeDirectory / file), continue);
|
EXPECT(std::filesystem::exists(homeDirectory / file), continue);
|
||||||
|
|
||||||
Util::File lhs(homeDirectory / file);
|
ruc::File lhs((homeDirectory / file).string());
|
||||||
EXPECT_EQ(lhs.data(), pushedFileContents.at(i));
|
EXPECT_EQ(lhs.data(), pushedFileContents.at(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,11 +938,11 @@ test data /**/ uncomment
|
|||||||
|
|
||||||
TEST_CASE(AddSystemDotfiles)
|
TEST_CASE(AddSystemDotfiles)
|
||||||
{
|
{
|
||||||
VERIFY(geteuid() == 0, return);
|
EXPECT(geteuid() == 0, return);
|
||||||
|
|
||||||
Config::the().setSystemDirectories({ "/etc", "/usr/lib" });
|
Config::the().setSystemPatterns({ "/etc/", "/usr/lib/" });
|
||||||
Dotfile::the().add({ "/etc/group", "/usr/lib/os-release" });
|
Dotfile::the().add({ "/etc/group", "/usr/lib/os-release" });
|
||||||
Config::the().setSystemDirectories({});
|
Config::the().setSystemPatterns({});
|
||||||
|
|
||||||
EXPECT(std::filesystem::exists("etc/group"));
|
EXPECT(std::filesystem::exists("etc/group"));
|
||||||
EXPECT(std::filesystem::exists("usr/lib/os-release"));
|
EXPECT(std::filesystem::exists("usr/lib/os-release"));
|
||||||
@@ -607,16 +953,16 @@ TEST_CASE(AddSystemDotfiles)
|
|||||||
|
|
||||||
TEST_CASE(PullSystemDotfiles)
|
TEST_CASE(PullSystemDotfiles)
|
||||||
{
|
{
|
||||||
VERIFY(geteuid() == 0, return);
|
EXPECT(geteuid() == 0, return);
|
||||||
|
|
||||||
createTestDotfiles({ "etc/group" }, { "" }, true);
|
createTestDotfiles({ "etc/group" }, { "" }, true);
|
||||||
|
|
||||||
Config::the().setSystemDirectories({ "/etc" });
|
Config::the().setSystemPatterns({ "/etc/" });
|
||||||
Dotfile::the().pull({ "etc/group" });
|
Dotfile::the().pull({ "etc/group" });
|
||||||
Config::the().setSystemDirectories({});
|
Config::the().setSystemPatterns({});
|
||||||
|
|
||||||
Util::File lhs("/etc/group");
|
ruc::File lhs("/etc/group");
|
||||||
Util::File rhs(Config::the().workingDirectory() / "etc/group");
|
ruc::File rhs((Config::the().workingDirectory() / "etc/group").string());
|
||||||
EXPECT_EQ(lhs.data(), rhs.data());
|
EXPECT_EQ(lhs.data(), rhs.data());
|
||||||
|
|
||||||
std::filesystem::remove_all(Config::the().workingDirectory() / "etc");
|
std::filesystem::remove_all(Config::the().workingDirectory() / "etc");
|
||||||
@@ -635,7 +981,7 @@ TEST_CASE(AddSymlinkDotfiles)
|
|||||||
|
|
||||||
EXPECT(std::filesystem::is_symlink(symlinkFileName));
|
EXPECT(std::filesystem::is_symlink(symlinkFileName));
|
||||||
EXPECT_EQ(std::filesystem::read_symlink(symlinkFileName).string(), fileInHome);
|
EXPECT_EQ(std::filesystem::read_symlink(symlinkFileName).string(), fileInHome);
|
||||||
EXPECT_EQ(Util::File(symlinkFileName).data(), "the file contents");
|
EXPECT_EQ(ruc::File(symlinkFileName.string()).data(), "the file contents");
|
||||||
|
|
||||||
removeTestDotfiles({ symlinkInHome, fileInHome });
|
removeTestDotfiles({ symlinkInHome, fileInHome });
|
||||||
}
|
}
|
||||||
@@ -654,7 +1000,7 @@ TEST_CASE(PullSymlinkDotfiles)
|
|||||||
|
|
||||||
EXPECT(std::filesystem::is_symlink(symlinkFileName));
|
EXPECT(std::filesystem::is_symlink(symlinkFileName));
|
||||||
EXPECT_EQ(std::filesystem::read_symlink(symlinkFileName).string(), fileInHome);
|
EXPECT_EQ(std::filesystem::read_symlink(symlinkFileName).string(), fileInHome);
|
||||||
EXPECT_EQ(Util::File(symlinkFileName).data(), "the file contents");
|
EXPECT_EQ(ruc::File(symlinkFileName.string()).data(), "the file contents");
|
||||||
|
|
||||||
removeTestDotfiles({ symlinkInHome, fileInHome });
|
removeTestDotfiles({ symlinkInHome, fileInHome });
|
||||||
}
|
}
|
||||||
@@ -672,7 +1018,7 @@ TEST_CASE(PushSymlinkDotfiles)
|
|||||||
|
|
||||||
EXPECT(std::filesystem::is_symlink(symlinkInHome));
|
EXPECT(std::filesystem::is_symlink(symlinkInHome));
|
||||||
EXPECT_EQ(std::filesystem::read_symlink(symlinkInHome).string(), fileInHome);
|
EXPECT_EQ(std::filesystem::read_symlink(symlinkInHome).string(), fileInHome);
|
||||||
EXPECT_EQ(Util::File(symlinkInHome).data(), "the file contents");
|
EXPECT_EQ(ruc::File(symlinkInHome.string()).data(), "the file contents");
|
||||||
|
|
||||||
removeTestDotfiles({ symlinkInHome, fileInHome });
|
removeTestDotfiles({ symlinkInHome, fileInHome });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,949 +0,0 @@
|
|||||||
#include <functional> // function
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "macro.h"
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/argparser.h"
|
|
||||||
|
|
||||||
bool runParser(std::vector<const char*> arguments, std::function<void(Util::ArgParser&)> initializer = {})
|
|
||||||
{
|
|
||||||
stdout = Test::TestSuite::the().outputNull();
|
|
||||||
|
|
||||||
Util::ArgParser parser;
|
|
||||||
if (initializer) {
|
|
||||||
initializer(parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
arguments.insert(arguments.begin(), "app");
|
|
||||||
auto result = parser.parse(arguments.size(), arguments.data());
|
|
||||||
|
|
||||||
stdout = Test::TestSuite::the().outputStd();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(NoArguments)
|
|
||||||
{
|
|
||||||
auto result = runParser({});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(NonExistentArguments)
|
|
||||||
{
|
|
||||||
auto result = runParser({ "-n", "-e" });
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
|
|
||||||
result = runParser({ "--non", "--existent" });
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
|
|
||||||
result = runParser({ "-n", "-e", "--non", "--existent" });
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
|
|
||||||
result = runParser({ "no", "handling" });
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(RequiredStringArguments)
|
|
||||||
{
|
|
||||||
// Single required string argument
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "my-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string argument, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
|
|
||||||
// Multiple required string arguments
|
|
||||||
stringArg1 = "";
|
|
||||||
std::string stringArg2 = "";
|
|
||||||
std::string stringArg3 = "";
|
|
||||||
result = runParser({ "my", "required", "argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my");
|
|
||||||
EXPECT_EQ(stringArg2, "required");
|
|
||||||
EXPECT_EQ(stringArg3, "argument");
|
|
||||||
|
|
||||||
// Multiple required string arguments, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
stringArg2 = "";
|
|
||||||
stringArg3 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
EXPECT_EQ(stringArg2, "");
|
|
||||||
EXPECT_EQ(stringArg3, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(OptionalStringArguments)
|
|
||||||
{
|
|
||||||
// Single optional string argument
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "my-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string argument, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
|
|
||||||
// Multiple optional string arguments
|
|
||||||
stringArg1 = "";
|
|
||||||
std::string stringArg2 = "";
|
|
||||||
std::string stringArg3 = "";
|
|
||||||
result = runParser({ "my", "optional", "argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my");
|
|
||||||
EXPECT_EQ(stringArg2, "optional");
|
|
||||||
EXPECT_EQ(stringArg3, "argument");
|
|
||||||
|
|
||||||
// Multiple optional string arguments, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
stringArg2 = "";
|
|
||||||
stringArg3 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
EXPECT_EQ(stringArg2, "");
|
|
||||||
EXPECT_EQ(stringArg3, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(NonRequiredStringArguments)
|
|
||||||
{
|
|
||||||
// Single non-required string argument
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "my-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my-non-required-argument");
|
|
||||||
|
|
||||||
// Single non-required string argument, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
|
|
||||||
// Multiple non-required string arguments
|
|
||||||
stringArg1 = "";
|
|
||||||
std::string stringArg2 = "";
|
|
||||||
std::string stringArg3 = "";
|
|
||||||
result = runParser({ "my", "non-required", "argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my");
|
|
||||||
EXPECT_EQ(stringArg2, "non-required");
|
|
||||||
EXPECT_EQ(stringArg3, "argument");
|
|
||||||
|
|
||||||
// Multiple non-required string arguments, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
stringArg2 = "";
|
|
||||||
stringArg3 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
EXPECT_EQ(stringArg2, "");
|
|
||||||
EXPECT_EQ(stringArg3, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(VectorStringArguments)
|
|
||||||
{
|
|
||||||
// Required vector string argument, not given
|
|
||||||
std::vector<std::string> vectorArg1 = {};
|
|
||||||
auto result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 0);
|
|
||||||
|
|
||||||
// Required vector string argument, one given
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "foo" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1[0], "foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string argument, two given
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "hello", "world" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 2);
|
|
||||||
if (vectorArg1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorArg1[0], "hello");
|
|
||||||
EXPECT_EQ(vectorArg1[1], "world");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(CombinationOfNonRequiredArguments)
|
|
||||||
{
|
|
||||||
// Optional arguments, one given
|
|
||||||
int intArg1 = 0;
|
|
||||||
double doubleArg1 = 0;
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "optional argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(intArg1, 0);
|
|
||||||
EXPECT_EQ(doubleArg1, 0);
|
|
||||||
EXPECT_EQ(stringArg1, "optional argument");
|
|
||||||
|
|
||||||
// Optional arguments, two given
|
|
||||||
intArg1 = 0;
|
|
||||||
doubleArg1 = 0;
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({ "999.999", "optional argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(intArg1, 0);
|
|
||||||
EXPECT_EQ(doubleArg1, 999.999);
|
|
||||||
EXPECT_EQ(stringArg1, "optional argument");
|
|
||||||
|
|
||||||
// Optional arguments, two given, one valid
|
|
||||||
intArg1 = 0;
|
|
||||||
doubleArg1 = 0;
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({ "999,999", "optional argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(intArg1, 0);
|
|
||||||
EXPECT_EQ(doubleArg1, 0);
|
|
||||||
EXPECT_EQ(stringArg1, "999,999");
|
|
||||||
|
|
||||||
// Optional arguments, two given, both valid but wrong order
|
|
||||||
stringArg1 = "";
|
|
||||||
intArg1 = 0;
|
|
||||||
doubleArg1 = 0;
|
|
||||||
result = runParser({ "999.999", "optional argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringArg1, "999.999");
|
|
||||||
EXPECT_EQ(intArg1, 0);
|
|
||||||
EXPECT_EQ(doubleArg1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(BoolOptions)
|
|
||||||
{
|
|
||||||
// Short option
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
auto result = runParser({ "-b" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Short option, not given
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
|
|
||||||
// Long option
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "--bool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Long option, not given
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
|
|
||||||
// Allow both short and long option, provide short
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "-b" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Allow both short and long option, provide long
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "--bool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Allow both short and long option, provide both
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "-b", "--bool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(SingleRequiredStringOptions)
|
|
||||||
{
|
|
||||||
// Single required string short option
|
|
||||||
std::string stringOpt1 = "";
|
|
||||||
auto result = runParser({ "-s", "my-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string short option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-smy-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string short option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-s" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single required string short option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single required string long option
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string", "my-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string long option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string=my-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string long option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single required string long option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(SingleOptionalStringOptions)
|
|
||||||
{
|
|
||||||
// Single optional string short option
|
|
||||||
std::string stringOpt1 = "";
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "-s", "my-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
EXPECT_EQ(stringArg1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string short option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-smy-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string short option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-s" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single optional string short option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single optional string long option
|
|
||||||
stringOpt1 = "";
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({ "--string", "my-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
EXPECT_EQ(stringArg1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string long option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string=my-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string long option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single optional string long option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(SingleNonRequiredStringOptions)
|
|
||||||
{
|
|
||||||
// Single non-required string short option
|
|
||||||
std::string stringOpt1 = "";
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "-s", "my-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
EXPECT_EQ(stringArg1, "my-non-required-argument");
|
|
||||||
|
|
||||||
// Single non-required string short option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-smy-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string short option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-s" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string short option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string long option
|
|
||||||
stringOpt1 = "";
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({ "--string", "my-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
EXPECT_EQ(stringArg1, "my-non-required-argument");
|
|
||||||
|
|
||||||
// Single non-required string long option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string=my-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string long option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string long option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(NumberOptions)
|
|
||||||
{
|
|
||||||
// Required int short option
|
|
||||||
int intOpt1 = 0;
|
|
||||||
auto result = runParser({ "-i", "2147483647" }, [&](auto& parser) {
|
|
||||||
parser.addOption(intOpt1, 'i', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(intOpt1, 2147483647);
|
|
||||||
|
|
||||||
// Required int short option, overflown value given
|
|
||||||
intOpt1 = 0;
|
|
||||||
result = runParser({ "-i", "2147483648" }, [&](auto& parser) {
|
|
||||||
parser.addOption(intOpt1, 'i', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(intOpt1, 0);
|
|
||||||
|
|
||||||
// Required int short option, empty given
|
|
||||||
intOpt1 = 0;
|
|
||||||
result = runParser({ "-i" }, [&](auto& parser) {
|
|
||||||
parser.addOption(intOpt1, 'i', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(intOpt1, 0);
|
|
||||||
|
|
||||||
// Required unsigned int short option
|
|
||||||
unsigned int unsignedIntOpt1 = 0;
|
|
||||||
result = runParser({ "-u", "4294967295" }, [&](auto& parser) {
|
|
||||||
parser.addOption(unsignedIntOpt1, 'u', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(unsignedIntOpt1, 4294967295);
|
|
||||||
|
|
||||||
// Required unsigned int short option, overflown value given
|
|
||||||
unsignedIntOpt1 = 0;
|
|
||||||
result = runParser({ "-u", "4294967296" }, [&](auto& parser) {
|
|
||||||
parser.addOption(unsignedIntOpt1, 'u', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(unsignedIntOpt1, 0);
|
|
||||||
|
|
||||||
// Required unsigned int short option, empty given
|
|
||||||
unsignedIntOpt1 = 0;
|
|
||||||
result = runParser({ "-u" }, [&](auto& parser) {
|
|
||||||
parser.addOption(unsignedIntOpt1, 'u', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(unsignedIntOpt1, 0);
|
|
||||||
|
|
||||||
// Required double short option
|
|
||||||
double doubleOpt1 = 0;
|
|
||||||
result = runParser({ "-d", "999.999" }, [&](auto& parser) {
|
|
||||||
parser.addOption(doubleOpt1, 'd', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(doubleOpt1, 999.999);
|
|
||||||
|
|
||||||
// Required double short option, empty given
|
|
||||||
doubleOpt1 = 0;
|
|
||||||
result = runParser({ "-d" }, [&](auto& parser) {
|
|
||||||
parser.addOption(doubleOpt1, 'd', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(doubleOpt1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(VectorStringOptions)
|
|
||||||
{
|
|
||||||
// Required vector string short option, not given
|
|
||||||
std::vector<std::string> vectorOpt1 = {};
|
|
||||||
auto result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 0);
|
|
||||||
|
|
||||||
// Required vector string short option, empty given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "-v" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 0);
|
|
||||||
|
|
||||||
// Required vector string short option, one given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "-v", "a vector argument!" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 1);
|
|
||||||
if (vectorOpt1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "a vector argument!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string short option, two given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "-v", "hello", "-v", "world" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 2);
|
|
||||||
if (vectorOpt1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "hello");
|
|
||||||
EXPECT_EQ(vectorOpt1[1], "world");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string short option, two given directly after
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "-vhello", "-vworld" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 2);
|
|
||||||
if (vectorOpt1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "hello");
|
|
||||||
EXPECT_EQ(vectorOpt1[1], "world");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string long option, empty given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "--vector" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 0);
|
|
||||||
|
|
||||||
// Required vector string long option, one given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "--vector", "a vector argument!" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 1);
|
|
||||||
if (vectorOpt1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "a vector argument!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string long option, two given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "--vector", "hello", "--vector", "world" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 2);
|
|
||||||
if (vectorOpt1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "hello");
|
|
||||||
EXPECT_EQ(vectorOpt1[1], "world");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string long option, two given directly after
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "--vector=hello", "--vector=world" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 2);
|
|
||||||
if (vectorOpt1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorOpt1.at(0), "hello");
|
|
||||||
EXPECT_EQ(vectorOpt1.at(1), "world");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(MultipleOptions)
|
|
||||||
{
|
|
||||||
// Both short options, second is required, with a non-option parameter in-between
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
std::string stringOpt1 = "";
|
|
||||||
std::vector<std::string> vectorArg1 = {};
|
|
||||||
auto result = runParser({ "-b", "something", "-s", "a-string-value" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "a-string-value");
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "something");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(StopOnDoubleDashOption)
|
|
||||||
{
|
|
||||||
// Bool short options, missing
|
|
||||||
// Expected: The bool options are interpreted as non-option parameters
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
bool boolOpt2 = false;
|
|
||||||
std::vector<std::string> vectorArg1 = {};
|
|
||||||
auto result = runParser({ "--", "-b", "-c" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 2);
|
|
||||||
if (vectorArg1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "-b");
|
|
||||||
EXPECT_EQ(vectorArg1.at(1), "-c");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool short options, one given
|
|
||||||
// Expected: boolOpt1 is set, one non-option parameter
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "-b", "--", "-c" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "-c");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool long options, missing
|
|
||||||
// Expected: The bool options are interpreted as non-option parameters
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "--", "--bool", "--cool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, '\0', "cool", nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 2);
|
|
||||||
if (vectorArg1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "--bool");
|
|
||||||
EXPECT_EQ(vectorArg1.at(1), "--cool");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool long options, one given
|
|
||||||
// Expected: boolOpt1 is set, one non-option parameter
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "--bool", "--", "--cool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, '\0', "cool", nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "--cool");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(StopOnFirstNonOption)
|
|
||||||
{
|
|
||||||
// Do not stop on first non-option; arguments are in correct order
|
|
||||||
// Expected: The bool options are set and one non-option parameter
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
bool boolOpt2 = false;
|
|
||||||
std::vector<std::string> vectorArg1 = {};
|
|
||||||
auto result = runParser({ "-b", "-c", "stopping" }, [&](auto& parser) {
|
|
||||||
parser.setStopParsingOnFirstNonOption(false);
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "stopping");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not stop on first non-option; arguments are in wrong order
|
|
||||||
// Expected: The bool options are set and one non-option parameter
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "-b", "stopping", "-c" }, [&](auto& parser) {
|
|
||||||
parser.setStopParsingOnFirstNonOption(false);
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "stopping");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop on first non option, arguments are in correct order
|
|
||||||
// Expected: The bool options are set and one non-option parameter
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "-b", "-c", "stopping" }, [&](auto& parser) {
|
|
||||||
parser.setStopParsingOnFirstNonOption(true);
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "stopping");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop on first non option, arguments are in wrong order
|
|
||||||
// Expected: boolOpt1 is set and the rest are non-option parameters
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "-b", "stopping", "-c" }, [&](auto& parser) {
|
|
||||||
parser.setStopParsingOnFirstNonOption(true);
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 2);
|
|
||||||
if (vectorArg1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "stopping");
|
|
||||||
EXPECT_EQ(vectorArg1.at(1), "-c");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(ExitOnFirstError)
|
|
||||||
{
|
|
||||||
// Do not stop on first error, one non-existing given
|
|
||||||
// Expected: parsing fails, boolOpt1 is set
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
auto result = runParser({ "--this-doesnt-exist", "--this-exist" }, [&](auto& parser) {
|
|
||||||
parser.setExitOnFirstError(false);
|
|
||||||
parser.addOption(boolOpt1, '\0', "this-exist", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Stop on first error, one non-existing given
|
|
||||||
// Expected: parsing fails, boolOpt1 is not set
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "--this-doesnt-exist", "--this-exist" }, [&](auto& parser) {
|
|
||||||
parser.setExitOnFirstError(true);
|
|
||||||
parser.addOption(boolOpt1, '\0', "this-exist", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#include <string>
|
|
||||||
|
|
||||||
#include "macro.h"
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/shell.h"
|
|
||||||
|
|
||||||
bool runShell(const char* command, std::string* output = nullptr)
|
|
||||||
{
|
|
||||||
stdout = Test::TestSuite::the().outputNull();
|
|
||||||
|
|
||||||
Util::Shell $;
|
|
||||||
auto exec = $(command);
|
|
||||||
if (output) {
|
|
||||||
*output = exec.output();
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout = Test::TestSuite::the().outputStd();
|
|
||||||
return !exec.status() ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(ShellCommand)
|
|
||||||
{
|
|
||||||
// Pipe test, grep section of the echo command, return true
|
|
||||||
std::string output = "";
|
|
||||||
auto result = runShell("echo 'Hello World!' | grep --only-matching 'Hello'", &output);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello\n");
|
|
||||||
|
|
||||||
// Return false
|
|
||||||
result = runShell("exit 1");
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
|
|
||||||
// Return false
|
|
||||||
result = runShell("failure() { return 1; }; failure");
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
#include <functional> // function
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "macro.h"
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/system.h"
|
|
||||||
|
|
||||||
bool runSystem(std::function<Util::System(Util::System&)> commands, std::string* output = nullptr, std::string* error = nullptr)
|
|
||||||
{
|
|
||||||
Util::System system;
|
|
||||||
auto exec = commands(system);
|
|
||||||
if (output) {
|
|
||||||
*output = exec.output();
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
*error = exec.error();
|
|
||||||
}
|
|
||||||
|
|
||||||
return !exec.status() ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(SystemCommand)
|
|
||||||
{
|
|
||||||
// Regular echo command, return true
|
|
||||||
std::string output = "";
|
|
||||||
std::string error = "";
|
|
||||||
auto result = runSystem([&](auto& $) {
|
|
||||||
return $("echo Hello World!")();
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello World!\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// Apend output of two echo commands, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("echo -n Hello ") + $("echo -n World!");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello World!");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// Pipe test, grep section of the echo command, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("echo Hello World!") | $("grep --only-matching Hello");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// Run all commands until first false, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("echo Hello") && $("echo World!");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello\nWorld!\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// Run all commands until first true, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("echo Hello") || $("echo World!");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// And plus pipe test, grep the middle word, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return ($("echo -n one ") && $("echo -n two ") && $("echo -n three")) | $("grep --only-matching two");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "two\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// FIXME waitpid does not seem to get the right exit status here
|
|
||||||
// Return false
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("exit 1")();
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(output, "");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
}
|
|
||||||
+1
Submodule vendor/ruc added at 243442a3b6
Reference in New Issue
Block a user