Compare commits

...
168 Commits
Author SHA1 Message Date
Riyyi 5d8e4f3625 Manager+Doc: Make package tracking more flexible, allow custom filenames 2025-02-03 16:37:13 +01:00
Riyyi add2138321 Manager: Fix unit test, add XML comment testcase 2025-02-02 22:07:51 +01:00
Riyyi 05aff91858 Manager: Fix crash when reading ENV variable as root 2025-02-02 17:51:47 +01:00
Riyyi e0ecd63dfd Manager: Allow pushing/pulling of the '.' directory 2025-01-24 22:04:12 +01:00
Riyyi 33ae6c57a2 Manager: Implement XML comments in selectively commenting feature 2025-01-24 21:53:59 +01:00
Riyyi 07882506c0 Manager: Add ability to detect Xorg/Wayland session for commenting files 2025-01-19 22:08:07 +01:00
Riyyi 9af4b778eb Meta: Update ruc library 2025-01-18 13:47:00 +01:00
Riyyi 2f89d5cfdc Doc: Update building instructions to include submodules 2022-09-14 21:01:42 +02:00
Riyyi 0e15bfe4bf Util+Test: Remove Util library 2022-09-13 11:16:51 +02:00
Riyyi 3d42a82003 Test: Switch from Util to the ruc library 2022-09-13 11:08:47 +02:00
Riyyi 4927e35bfc Manager: Switch from Util to the ruc library 2022-09-13 11:08:47 +02:00
Riyyi 2b3acd5d51 CMake: Build ruc library 2022-09-13 11:08:47 +02:00
Riyyi 6f2af1a40d Meta: Add submodule for ruc library 2022-09-13 01:34:09 +02:00
Riyyi 6f0e3d6063 Util: Add Container parser checks 2022-08-09 11:11:19 +02:00
Riyyi 3e2819cf93 Util: Use char literals instead of numbers in enums 2022-08-09 10:56:48 +02:00
Riyyi 5e6ec1eff3 Test: Make formatting tests more complete 2022-08-08 21:45:55 +02:00
Riyyi 14e57acfd8 Util: Add format specifier logic to container types 2022-08-08 21:44:24 +02:00
Riyyi 5cf8d18742 Util: Add container type to Parser 2022-08-08 21:42:50 +02:00
Riyyi 086da50a57 Util: Fix Builder::putU64() when printing unaligned zero-padded hexes 2022-08-08 21:19:47 +02:00
Riyyi 36a18f65c1 Util: Add default formatting type to floating points checks 2022-08-06 19:53:45 +02:00
Riyyi ea853c1ace Util: Fix alternative form for octal types 2022-08-06 16:45:36 +02:00
Riyyi 448ad42005 Util: Add integral formatting to char and bool types 2022-08-06 00:39:03 +02:00
Riyyi 80c37496e8 Util: Implement specifier option checks 2022-08-05 23:38:08 +02:00
Riyyi a75bb863ff Util: Fix brace checks in Parser 2022-08-05 22:01:54 +02:00
Riyyi dc8d2a49cd Util: Call integral formatter from pointer type 2022-08-05 21:59:40 +02:00
Riyyi 8b4a748c9d Util: Add specifier options checks for pointer types 2022-08-05 20:27:49 +02:00
Riyyi 175251496f Util: Use putU64 when formatting pointer types 2022-08-05 02:08:40 +02:00
Riyyi 1fda63ae78 Util: Use C++20 designated initializer lists on structs 2022-08-05 00:48:21 +02:00
Riyyi 6c6ddf936a Util: Add alternative form specifier option to integral types 2022-08-05 00:13:09 +02:00
Riyyi bd8367db0e Util: Add newlines to long parameter list 2022-08-05 00:01:33 +02:00
Riyyi b07b9d92d4 Util: Add char to integral presentation types 2022-08-04 23:44:55 +02:00
Riyyi 7a98f8e79f Util: Add integral types and capitalization specifier options 2022-08-04 22:52:06 +02:00
Riyyi 25287bc370 Util: Add zero padding specifier to putU64() 2022-08-04 19:31:30 +02:00
Riyyi 5eb5e2d51e Util: Reorder specifier arguments to match parsing order 2022-08-04 19:30:44 +02:00
Riyyi c40650ab67 Util: Implement some integral specifiers 2022-08-04 17:06:44 +02:00
Riyyi 9758c3e425 Util: Use C++20 concepts for integral and floating-point types 2022-08-04 16:05:49 +02:00
Riyyi 7cff165f08 Util: Add specifier type checking 2022-08-04 01:35:10 +02:00
Riyyi 7825e87c19 Util: Rename Parser SpecifierType -> ParameterType 2022-08-04 01:25:23 +02:00
Riyyi 04250a8605 Util: Add default alignment to string types 2022-08-03 16:05:01 +02:00
Riyyi 5e419f501d Util: Change nullptr stringification 2022-08-03 15:49:43 +02:00
Riyyi 1b7a736be2 Util: Pass string width,align,fill option to Format::Builder 2022-08-03 14:05:34 +02:00
Riyyi 6209251ca6 Util: Pass floating-point precision to Format::Builder 2022-08-03 12:53:28 +02:00
Riyyi a118f3da3c Util: Add format string parsing 2022-08-03 12:51:33 +02:00
Riyyi 8049477795 Util: Update Format::Builder floating-point precision 2022-08-03 12:06:35 +02:00
Riyyi f08225136f Util: Fix stringToNumber in Format::Parser 2022-08-03 11:47:54 +02:00
Riyyi bbb8691c90 Util: Fix parsing index in automatic mode 2022-08-03 11:00:58 +02:00
Riyyi da0954ffcc Util: Add map formatting to new struct Formatter 2022-08-03 01:51:21 +02:00
Riyyi f3344a0fb9 Util: Switch from format() to struct Formatter 2022-08-03 01:02:33 +02:00
Riyyi eb25e98f79 Util: Rename logging type enum, Type -> LogType 2022-08-03 00:27:37 +02:00
Riyyi 5b85feec19 Util: Forward declare where possible in format.h 2022-08-03 00:27:27 +02:00
Riyyi c4e7fa3375 Util: Improve check on formatting recursion for asserts 2022-08-01 00:11:11 +02:00
Riyyi b574a79dfe Manager+Util: Change enum class type from int to uint8_t (4 -> 1 byte) 2022-07-28 10:57:41 +02:00
Riyyi f58113e257 Util: Add ability to print log messages 2022-07-28 01:41:00 +02:00
Riyyi c90369181f Util: Split formatting print into print and colored print 2022-07-28 01:32:27 +02:00
Riyyi 187e082018 Util: Rename FormatAngleBracket -> FormatOperatorStyle 2022-07-28 01:19:09 +02:00
Riyyi 3837ebef06 Util: Print to provided file stream 2022-07-27 01:17:44 +02:00
Riyyi 94906028fc Util: Remove template non-type parameter default arg, it cant be empty 2022-07-27 01:06:29 +02:00
Riyyi c59bfc20f7 Manager+Util: Switch to custom assert macro 2022-07-27 00:10:11 +02:00
Riyyi 04f5f42bb5 Util: Add extra safety check for custom asserts 2022-07-26 23:47:47 +02:00
Riyyi 33df4bef39 Util+Test: Separate formatting and printing into different files 2022-07-26 23:25:20 +02:00
Riyyi e8c4b6eb11 Util: Rename formatting danger -> critical 2022-07-26 16:27:09 +02:00
Riyyi 15a524adbb Util: Reset IOstreams flags after usage 2022-07-26 14:06:29 +02:00
Riyyi c3776fb967 Util: Store formatting parameters in a non-variadic container 2022-07-26 12:57:55 +02:00
Riyyi a55bb27641 Manager+Doc: Update config file 2022-07-25 17:35:53 +02:00
Riyyi 0e93009e4b Util: Update JSON user-defined literal type name 2022-07-25 17:19:56 +02:00
Riyyi f536aefc99 Util: Add missing include 2022-07-22 22:54:10 +02:00
Riyyi 9390f4fd31 Util: Add custom assert 2022-07-22 22:44:47 +02:00
Riyyi 7d2c2c52e2 Util: Add compiler detection using preprocessor directives 2022-07-22 22:27:22 +02:00
Riyyi bcea91a2c0 Util: Add newline to string line formatting function 2022-07-22 21:51:03 +02:00
Riyyi fc163f7f06 Meta: Bump C++ version from 17 to 20 2022-07-22 21:26:33 +02:00
Riyyi 9857f11798 Util: Remove comparison of unsigned expression, it is always true 2022-07-22 21:26:26 +02:00
Riyyi da5dce22e6 Meta: Update .clang-format to indent preprocessor directives 2022-07-22 15:32:09 +02:00
Riyyi 8345f67c50 Everywhere: Switch to #pragma once, add missing copyright headers 2022-07-22 13:04:04 +02:00
Riyyi bba2b30cbb Util: Add helper Util::Json to Util::JSON::Value 2022-07-22 12:42:33 +02:00
Riyyi deb9f29b47 Util+Test: Change namespace of JSON classes: Json -> Util::JSON 2022-07-22 12:41:27 +02:00
Riyyi be74d89fea Test: Add string formatting tests 2022-07-22 12:06:42 +02:00
Riyyi 75034a2c5a Util: Implement Json::Value type customization for print formatting 2022-07-22 02:13:12 +02:00
Riyyi 835481f4a5 Util: Add formatting and type printing capability
Extending the type compatibility is done with an easy to use API.
2022-07-22 01:59:19 +02:00
Riyyi 9b3489676e Util: Add two functions to GenericLexer
- tellRemaining
- consumeSpecific
2022-07-22 01:48:14 +02:00
Riyyi dd209a2609 Util: Move json/conversion.h -> meta/odr.h 2022-07-21 01:50:15 +02:00
Riyyi 2408be335d Util: Rename two functions to be more in line with GenericLexer 2022-07-20 23:34:31 +02:00
Riyyi 42143fed71 Util: Rename Lexer/Parser get functions to consume 2022-07-20 22:57:35 +02:00
Riyyi 8dd3f11d7b Util: Run clang-format on Json::Lexer 2022-07-20 22:49:41 +02:00
Riyyi e3041bfdc7 Util: Implement GenericLexer in Json::Lexer 2022-07-20 22:49:41 +02:00
Riyyi 4ce026ec78 Util: Add GenericLexer class 2022-07-20 22:49:41 +02:00
Riyyi 4e6c5ca341 Util: Change Job m_input string -> string_view 2022-07-20 21:32:17 +02:00
Riyyi b4100113fe Util: Add missing include 2022-07-20 21:30:15 +02:00
Riyyi 11fdc64447 Util: Add pause() and resume() to Timer 2022-07-18 17:37:04 +02:00
Riyyi a92ac29d19 Util: Add parse(std::ifstream) function 2022-07-18 16:03:11 +02:00
Riyyi 07df4054da Util: Massively speedup serialization, remove all ifs from loop 2022-07-18 15:53:42 +02:00
Riyyi 2915c1c1ec Util: Reorder Array/Object functions, add empty() 2022-07-18 15:10:11 +02:00
Riyyi 161be80fd6 Util: Make second to last element detection more compatible
std::prev doesn't work on std::unordered_map, this patch makes it work.
2022-07-18 12:50:09 +02:00
Riyyi c3f5df7a29 Util: Optimize away returning strings 2022-07-18 12:42:08 +02:00
Riyyi 93eb2f4f6a Util: Optimize Value member away 2022-07-18 12:18:56 +02:00
Riyyi 99759b82b7 Util: Optimize more string creating and string.find() away 2022-07-17 22:03:48 +02:00
Riyyi 827286a0b2 Util: Optimize string creation and string.find() away 2022-07-17 21:16:53 +02:00
Riyyi 9f31a04909 Util: Optimize Lexer::getNumberOrLiteral() to one string append 2022-07-17 20:40:05 +02:00
Riyyi 3e69abdfa0 Util: Implement copy-and-swap idiom and the Rule of Five in Value 2022-07-16 00:51:32 +02:00
Riyyi a1bb1b93c9 Test: Hide JSON stderr output 2022-07-15 13:10:06 +02:00
Riyyi 7c1f95e850 Util: Prevent unnecessary copy assignment calls 2022-07-15 12:47:08 +02:00
Riyyi 0841e49b46 Util: Add user-defined string literal for Json::Value 2022-07-14 20:39:37 +02:00
Riyyi 3ea87a1d28 Util: Print Json error messages to stderr 2022-07-14 15:36:53 +02:00
Riyyi 4ea1242247 Manager: Switch to own JSON parser, remove nlohmann/json 2022-07-12 21:24:58 +02:00
Riyyi d6cd90ae4a Util: Rename Json::Value m_value union variables 2022-07-12 12:36:03 +02:00
Riyyi b76223693b Util: Add Parser and Serializer to Value friends 2022-07-12 12:07:49 +02:00
Riyyi ad137576a8 Util: Make character breaks in Lexer more clear syntactically 2022-07-12 11:17:34 +02:00
Riyyi 5835c63bda Util: Rename Array m_values -> m_elements 2022-07-12 11:06:46 +02:00
Riyyi aa283f8c75 Test: Add Json test cases 2022-07-12 02:28:22 +02:00
Riyyi 39329caf22 Util: Fix empty Object {} parsing 2022-07-12 02:00:49 +02:00
Riyyi a33f35faab Util: Rearrange Parser functions 2022-07-12 01:54:15 +02:00
Riyyi aa5b5117ad Util: Change seekForward() to go 1 step after the match 2022-07-12 01:54:15 +02:00
Riyyi 51e788a5ad Util: Change Token element access to use at() 2022-07-12 01:54:15 +02:00
Riyyi c7e4eb2575 Util: Add EOF detection in Parser getArray() getObject() 2022-07-12 01:54:15 +02:00
Riyyi 55ed70353a Util: Fix trailing comma in Object error message 2022-07-11 19:33:54 +02:00
Riyyi abb075939f Util: Properly detect empty Tokens and multiple root elements 2022-07-11 19:08:30 +02:00
Riyyi a3ab6aecfa Util: Fix comma placement in error message 2022-07-11 00:49:27 +02:00
Riyyi 097be4c012 Util: Fix Serializer trailing commas in Object dumping
The .end() returns an iterator one past the last pair, so it would
always add a comma after the last member.
2022-07-10 21:42:48 +02:00
Riyyi ea7049306e Util: Fix empty Array parsing 2022-07-10 16:13:44 +02:00
Riyyi fc3dc936fa Util: Fix line count detection for non-trailing newline in Job 2022-07-10 15:58:50 +02:00
Riyyi f2ea84be0f Util: Fix to break at the end of the file in Lexer 2022-07-10 15:49:34 +02:00
Riyyi 003db7332d Util: Add fromJson() functions for Object Value types 2022-07-10 00:56:39 +02:00
Riyyi 8797f695e0 Util: Fix type check 2022-07-10 00:41:28 +02:00
Riyyi 74dd24a516 Util: Add flexible constructor toJson() to Json::Value 2022-07-10 00:33:33 +02:00
Riyyi 5c95288874 Util: Move staticConst<T> to separate header 2022-07-10 00:30:31 +02:00
Riyyi 0c972f420c Util: Add get<T>() functionality with customization points to Value 2022-07-08 14:23:38 +02:00
Riyyi c5bbd7befd Util: Add exists() to Json::Value 2022-07-08 14:11:03 +02:00
Riyyi a209452a68 Util: Add Json::Value at() functions 2022-07-08 13:55:40 +02:00
Riyyi 4487c800df Util: Change Array constructor
std::vector supports both vector and initializer_list for construction.
2022-07-08 13:31:01 +02:00
Riyyi d9cfd3f7c1 Util: Forward declare Json::Value in Array and Object class 2022-07-08 13:29:23 +02:00
Riyyi 0465d5802a Util: Add size() getter to Json::Value 2022-07-07 09:47:34 +02:00
Riyyi 66774364fd Util: Forward declare Value in the Parser class 2022-07-06 10:51:27 +02:00
Riyyi 3078f62162 Util: Remove unneeded std::prev() use 2022-07-06 10:50:23 +02:00
Riyyi 8e92317f9e Util: Fix parsing solidus '/' 2022-07-06 10:23:43 +02:00
Riyyi c3d6af85ba Util: Rename Stringify -> Serializer 2022-07-04 17:04:16 +02:00
Riyyi 5d844554f5 Util: Fix Parser number error detection 2022-07-01 11:49:20 +02:00
Riyyi a3d1cd1d74 Util: Change Lexer line/column to 0-based 2022-07-01 11:43:15 +02:00
Riyyi ed5efdb0d6 Util: Remove unused function 2022-07-01 11:36:11 +02:00
Riyyi 4457a711cd Util: Remove debugging printf() statements 2022-07-01 11:32:26 +02:00
Riyyi 925fdd474a Util: Optimize m_index variable usage 2022-07-01 11:32:26 +02:00
Riyyi d65f56bf03 Util: Break Lexer strings on quote and new lines 2022-07-01 11:32:26 +02:00
Riyyi 4501061060 Util: Add string validation to Parser 2022-07-01 11:14:15 +02:00
Riyyi 0aef4ee2c0 Util: Only break Lexer string tokenization on unescaped " 2022-07-01 11:11:42 +02:00
Riyyi c1c9429a9d Util: Improve Lexer string tokenization 2022-06-30 20:46:11 +02:00
Riyyi 356cdaf051 Util: Add increment() and decrement() to Lexer 2022-06-30 20:46:11 +02:00
Riyyi b41bba72c9 Util: Rename Object key -> name 2022-06-30 20:46:08 +02:00
Riyyi a818c4489c Util: Add missing getter to Object 2022-06-30 20:45:33 +02:00
Riyyi 8800ff4f7a Util: Move literal validation logic to Parser 2022-06-30 20:45:33 +02:00
Riyyi 6161d577c6 Util: Change Object map unused value to uint8_t 2022-06-30 20:45:33 +02:00
Riyyi ddf0a2858b Util: Move number validation logic to Parser 2022-06-30 20:45:26 +02:00
Riyyi 8c08b8a594 Util: Add error report to Lexer default case 2022-06-29 14:14:09 +02:00
Riyyi 4a7ed92519 Util: Add constructor Value(Type) 2022-06-28 16:25:19 +02:00
Riyyi 95e0ddf49a Util: Fix return Value when an error has occurred 2022-06-28 16:24:13 +02:00
Riyyi fdeba07fd4 Util: Improve JSON parsing and add error messages 2022-06-28 16:24:13 +02:00
Riyyi a14cd1e0a5 Util: Change Parser switch cases to if-else 2022-06-28 16:11:33 +02:00
Riyyi 061ed74d4f Util: Check strings for double quotes in Lexer 2022-06-28 16:03:43 +02:00
Riyyi 8fb1a1a8e9 Util: Move vector<Token> ownership to Job class 2022-06-26 23:13:25 +02:00
Riyyi bc01b34e58 Util: Calculate JSON line number before lexing 2022-06-25 14:05:45 +02:00
Riyyi 68bc95fdf1 Util: Implement new JSON Job class 2022-06-24 14:56:37 +02:00
Riyyi 13d020a351 Util: Add JSON Job class
This class holds the to parse string and has a helper for printing error
messages.
2022-06-24 14:46:21 +02:00
Riyyi af678374bc Util: Add emplace() to Json::Value 2022-06-23 14:54:29 +02:00
Riyyi e5978310bf Util: Fix Parser Json::Value construction 2022-06-23 14:19:06 +02:00
Riyyi 8bfae9b483 Util: Add more ways of accessing and creating Json::Value objects 2022-06-23 13:47:07 +02:00
Riyyi 9f7fe81ef6 Util: Add parse() and dump() to Json::Value 2022-06-23 13:45:07 +02:00
Riyyi aad95de5fd Util: Add a JSON stringifier 2022-06-22 14:23:29 +02:00
Riyyi c385432bb0 Util: Add JSON accessors 2022-06-16 11:38:22 +02:00
Riyyi 5188d57d19 Util: Add JSON parser 2022-06-07 12:46:13 +02:00
Riyyi 0880d98fe0 Util: Add JSON Array/Object/Value types 2022-06-07 12:46:13 +02:00
Riyyi 1c676f9548 Util: Add JSON lexical analyzer 2022-06-07 12:46:13 +02:00
36 changed files with 429 additions and 2992 deletions
+2 -1
View File
@@ -17,6 +17,7 @@ AllowAllParametersOfDeclarationOnNextLine: false
AllowShortLambdasOnASingleLine: All
AlwaysBreakTemplateDeclarations: Yes
IndentPPDirectives: BeforeHash
BraceWrapping:
AfterEnum: false
@@ -32,7 +33,7 @@ SpaceAfterTemplateKeyword: false
SpaceInEmptyBlock: false
NamespaceIndentation: None
FixNamespaceComments: true
Standard: c++17
Standard: c++20
TabWidth: 4
UseTab: AlignWithSpaces
...
+3
View File
@@ -0,0 +1,3 @@
[submodule "vendor/ruc"]
path = vendor/ruc
url = https://github.com/Riyyi/ruc
+29 -19
View File
@@ -4,6 +4,9 @@
# Set project name
set(PROJECT "manafiles")
# Unit tests
option(MANAFILES_BUILD_TESTS "Build the Manafiles test programs" ON)
# ------------------------------------------
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
@@ -15,7 +18,7 @@ include(GNUInstallDirs)
# ------------------------------------------
# Setup C++ compiler
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
@@ -50,29 +53,23 @@ endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# ------------------------------------------
# Configure source
# Library
# Define include files
include_directories(
"src"
"test"
)
set(RUC_BUILD_TESTS OFF)
add_subdirectory("vendor/ruc")
# ------------------------------------------
# Application target
# Define source files
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
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})
target_link_libraries(${PROJECT})
target_include_directories(${PROJECT} PRIVATE
"src"
"vendor/ruc/src")
target_link_libraries(${PROJECT} ruc)
install(TARGETS ${PROJECT}
DESTINATION ${CMAKE_INSTALL_BINDIR}
@@ -88,8 +85,21 @@ add_dependencies(run ${PROJECT})
# ------------------------------------------
# Unit test target
add_executable(test ${TEST_SOURCES})
target_link_libraries(test)
if (MANAFILES_BUILD_TESTS)
# 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
+50 -13
View File
@@ -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]].
Alternatively, you will have to [[#building][build from source]].
Alternatively, you will have to [[#compiling][build from source]].
*** Configuration
@@ -66,6 +66,7 @@ The system patterns from the example config:
"systemPatterns": [
"/boot/",
"/etc/",
"/usr/lib/",
"/usr/share/"
]
#+END_SRC
@@ -85,44 +86,80 @@ or uncomments them depending on the machine's configuration. Such a block looks
like this:
#+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"
# <<<
#+END_SRC
This block uses all of the supported configurations, ~distro~, ~hostname~ and
~user~. These can be used in any combination and order.
This block uses all of the supported configurations, ~distro~, ~hostname~,
~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
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.
** Building
***** Usable values for the configurations
*** Build dependencies
| distro | session |
|--------+---------|
| arch | xorg |
| debian | wayland |
| ubuntu | |
** Compiling
*** Dependencies
- ~gcc-libs~
- (make) ~cmake~
- (make) ~git~
- (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) ~pacman~ + ~pacman-contrib~
- (optional) ~apt~ + ~dpkg~
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
$ mkdir build
$ cd build
$ cmake ..
$ make
#+END_SRC
*** Build installation
#+BEGIN_SRC shell-script
# Optional. This will install the manafiles executable in /usr/bin
$ sudo make install
#+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
View File
@@ -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
manafiles \- config file and package tracking utility
@@ -31,7 +31,7 @@ See File Options below.
.TP
.BR \-P ", " \-\-package
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. \
See Package Options below.
@@ -67,8 +67,10 @@ Install all AUR packages of the stored list.
Install all official packages of the stored list.
.TP
.BR \-s ", " \-\-store
Stores a list of all installed packages on the system.
.BR \-s ", " \-\-search
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
Usage examples:
+1
View File
@@ -10,6 +10,7 @@
"systemPatterns": [
"/boot/",
"/etc/",
"/usr/lib/",
"/usr/share/"
]
}
+12 -10
View File
@@ -11,9 +11,9 @@
#include <string>
#include <vector>
#include <nlohmann/json.hpp>
#include "config.h"
#include "ruc/json/json.h"
#include "ruc/meta/assert.h"
Config::Config(s)
: m_workingDirectory(std::filesystem::current_path())
@@ -51,7 +51,7 @@ void Config::parseConfigFile()
return;
}
nlohmann::json json;
ruc::Json json;
std::ifstream file(m_config);
if (!file.is_open()) {
@@ -72,21 +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 {
{ "ignorePatterns", settings.ignorePatterns },
{ "systemPatterns", settings.systemPatterns }
};
}
void from_json(const nlohmann::json& object, Settings& settings)
void fromJson(const ruc::Json& json, Settings& settings)
{
if (object.find("ignorePatterns") != object.end()) {
object.at("ignorePatterns").get_to(settings.ignorePatterns);
VERIFY(json.type() == ruc::Json::Type::Object);
if (json.exists("ignorePatterns")) {
json.at("ignorePatterns").getTo(settings.ignorePatterns);
}
if (object.find("systemPatterns") != object.end()) {
object.at("systemPatterns").get_to(settings.systemPatterns);
if (json.exists("systemPatterns")) {
json.at("systemPatterns").getTo(settings.systemPatterns);
}
}
+8 -11
View File
@@ -4,17 +4,15 @@
* SPDX-License-Identifier: MIT
*/
#ifndef CONFIG_H
#define CONFIG_H
#pragma once
#include <cstddef> // size_t
#include <filesystem> // path
#include <string>
#include <vector>
#include "nlohmann/json.hpp"
#include "util/singleton.h"
#include "ruc/json/json.h"
#include "ruc/singleton.h"
struct Settings {
std::vector<std::string> ignorePatterns {
@@ -28,11 +26,12 @@ struct Settings {
std::vector<std::string> systemPatterns {
"/boot/",
"/etc/",
"/usr/lib/",
"/usr/share/"
};
};
class Config : public Util::Singleton<Config> {
class Config : public ruc::Singleton<Config> {
public:
Config(s);
virtual ~Config();
@@ -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 from_json(const nlohmann::json& object, Settings& settings);
#endif // CONFIG_H
void toJson(ruc::Json& object, const Settings& settings);
void fromJson(const ruc::Json& object, Settings& settings);
+55 -21
View File
@@ -1,10 +1,9 @@
/*
* Copyright (C) 2021-2022 Riyyi
* Copyright (C) 2021-2022,2025 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cassert> // assert
#include <cctype> // tolower
#include <cstddef> // size_t
#include <cstdio> // fprintf, printf, stderr
@@ -16,10 +15,12 @@
#include <unistd.h> // geteuid, getlogin, setegid, seteuid
#include <vector>
#include "ruc/file.h"
#include "ruc/meta/assert.h"
#include "config.h"
#include "dotfile.h"
#include "machine.h"
#include "util/file.h"
Dotfile::Dotfile(s)
{
@@ -107,14 +108,19 @@ void Dotfile::push(const std::vector<std::string>& targets)
bool Dotfile::match(const std::string& path, const std::vector<std::string>& patterns)
{
assert(path.front() == '/');
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;
}
@@ -373,41 +379,60 @@ void Dotfile::sync(SyncType type,
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=",
"hostname=",
"user=",
"session=",
};
// State of the loop
bool isFiltering = false;
std::string filter[3];
std::string filter[4];
std::string commentCharacter;
std::string commentTerminationCharacter;
size_t positionInFile = 0;
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 whiteSpaceBeforeComment = line.find_first_not_of(" \t");
size_t contentAfterComment = line.find_first_not_of(" \t" + commentCharacter);
// NOTE: The +1 is needed to take the newline into account
size_t contentLength = lineLength + 1 - contentAfterComment - (lineLength - line.find_last_not_of(" \t" + commentTerminationCharacter));
size_t commentStart = line.find(commentCharacter, indentation);
size_t commentEnd = line.rfind(commentTerminationCharacter);
bool hasComment = commentStart != std::string::npos && (commentTerminationCharacter.empty() || commentEnd != std::string::npos);
// If there was no comment, grab whitespace correctly
if (whiteSpaceBeforeComment == std::string::npos) {
whiteSpaceBeforeComment = contentAfterComment;
// Lines that have a comment at the *end* of the line aren't considered commented lines
if (hasComment && line.find_first_not_of(" \t") < commentStart) {
hasComment = false;
}
if (!addComment) {
line = line.substr(0, whiteSpaceBeforeComment)
+ line.substr(contentAfterComment, contentLength);
// Uncomment line
if (hasComment && !addComment) {
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 {
line = line.substr(0, whiteSpaceBeforeComment)
// Comment line
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 + ' '
+ line.substr(contentAfterComment, contentLength)
+ line.substr(contentStart, contentLength)
+ (!commentTerminationCharacter.empty() ? ' ' + commentTerminationCharacter : "");
}
@@ -420,7 +445,7 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
if (line.find(">>>") != std::string::npos) {
// Find machine info
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();
if (find < search[i].size()) {
continue;
@@ -435,6 +460,10 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
if (i > 0 && commentCharacter.at(i - 1) == '/' && commentCharacter.at(i) == '*') {
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
if (commentCharacter.at(i) == ' ' || commentCharacter.at(i) == '\t') {
commentCharacter.erase(i, 1);
@@ -450,6 +479,7 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
filter[0] = "";
filter[1] = "";
filter[2] = "";
filter[3] = "";
commentCharacter.clear();
commentTerminationCharacter.clear();
continue;
@@ -471,6 +501,10 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
commentOrUncommentLine(line, true);
continue;
}
else if (filter[3] != Machine::the().session() && !filter[3].empty()) {
commentOrUncommentLine(line, true);
continue;
}
commentOrUncommentLine(line, false);
}
+5 -7
View File
@@ -4,23 +4,23 @@
* SPDX-License-Identifier: MIT
*/
#ifndef DOTFILE_H
#define DOTFILE_H
#pragma once
#include <cstddef> // size_t
#include <cstdint> // uint8_t
#include <filesystem>
#include <functional> // function
#include <string>
#include <vector>
#include "util/singleton.h"
#include "ruc/singleton.h"
class Dotfile : public Util::Singleton<Dotfile> {
class Dotfile : public ruc::Singleton<Dotfile> {
public:
Dotfile(s);
virtual ~Dotfile();
enum class SyncType {
enum class SyncType : uint8_t {
Add,
Pull,
Push,
@@ -43,5 +43,3 @@ private:
void forEachDotfile(const std::vector<std::string>& targets, const std::function<void(const std::filesystem::directory_entry&, size_t)>& callback);
};
#endif // DOTFILE_H
+87 -3
View File
@@ -1,21 +1,25 @@
/*
* Copyright (C) 2022 Riyyi
* Copyright (C) 2022,2025 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstdint> // int8_t
#include <filesystem> // std::filesystem::path
#include <pwd.h> // getpwnam
#include <sstream> // istringstream
#include <unistd.h> // gethostname, getlogin
#include "ruc/file.h"
#include "machine.h"
#include "util/file.h"
Machine::Machine(s)
{
fetchDistro();
fetchHostname();
fetchUsername();
fetchSession();
}
Machine::~Machine()
@@ -26,7 +30,7 @@ Machine::~Machine()
void Machine::fetchDistro()
{
Util::File osRelease("/etc/os-release");
ruc::File osRelease("/etc/os-release");
std::istringstream stream(osRelease.data());
for (std::string line; std::getline(stream, line);) {
if (line.find("ID=") == 0) {
@@ -61,3 +65,83 @@ void Machine::fetchUsername()
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
View File
@@ -4,37 +4,38 @@
* SPDX-License-Identifier: MIT
*/
#ifndef MACHINE_H
#define MACHINE_H
#pragma once
#include <cstdint> // uint32_t
#include <pwd.h> // passwd
#include <string>
#include "util/singleton.h"
#include "ruc/singleton.h"
class Machine : public Util::Singleton<Machine> {
class Machine : public ruc::Singleton<Machine> {
public:
Machine(s);
virtual ~Machine();
const std::string& distroId() { return m_distroId; }
const std::string& distroIdLike() { return m_distroIdLike; }
const std::string& hostname() { return m_hostname; }
const std::string& distroId() const { return m_distroId; }
const std::string& distroIdLike() const { return m_distroIdLike; }
const std::string& hostname() const { return m_hostname; }
std::string username() { return m_passwd->pw_name; }
uint32_t uid() { return m_passwd->pw_uid; }
uint32_t gid() { return m_passwd->pw_gid; }
std::string username() const { return std::string(m_passwd->pw_name); }
uint32_t uid() const { return m_passwd->pw_uid; }
uint32_t gid() const { return m_passwd->pw_gid; }
const std::string& session() const { return m_session; }
private:
void fetchDistro();
void fetchHostname();
void fetchUsername();
void fetchSession();
std::string m_distroId;
std::string m_distroIdLike;
std::string m_hostname;
std::string m_session;
passwd* m_passwd { nullptr };
};
#endif // MACHINE_H
+17 -19
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2022 Riyyi
* Copyright (C) 2021-2022,2025 Riyyi
*
* SPDX-License-Identifier: MIT
*/
@@ -8,11 +8,12 @@
#include <string>
#include <vector>
#include "ruc/argparser.h"
#include "ruc/timer.h"
#include "config.h"
#include "dotfile.h"
#include "package.h"
#include "util/argparser.h"
#include "util/timer.h"
int main(int argc, const char* argv[])
{
@@ -23,12 +24,12 @@ int main(int argc, const char* argv[])
bool addOrAur = false;
bool install = false;
bool pull = false;
bool pushOrStore = false;
bool pushOrSearch = false;
bool verbose = false;
std::vector<std::string> targets {};
Util::ArgParser argParser;
ruc::ArgParser argParser;
argParser.addOption(fileOperation, 'F', "file", nullptr, nullptr);
argParser.addOption(packageOperation, 'P', "package", 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(install, 'i', "install", 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.addArgument(targets, "targets", nullptr, nullptr, Util::ArgParser::Required::No);
argParser.addArgument(targets, "targets", nullptr, nullptr, ruc::ArgParser::Required::No);
argParser.parse(argc, argv);
if (fileOperation + packageOperation + helpOperation > 1) {
@@ -48,7 +49,7 @@ int main(int argc, const char* argv[])
}
#ifndef NDEBUG
Util::Timer t;
ruc::Timer t;
#endif
Config::the().setVerbose(verbose);
@@ -60,32 +61,29 @@ int main(int argc, const char* argv[])
if (pull) {
Dotfile::the().pull(targets);
}
if (pushOrStore) {
if (pushOrSearch) {
Dotfile::the().push(targets);
}
if (!addOrAur && !pull && !pushOrStore) {
if (!addOrAur && !pull && !pushOrSearch) {
Dotfile::the().list(targets);
}
}
else if (packageOperation) {
if (addOrAur) {
Package::the().aurInstall();
Package::the().aurInstall(targets);
}
if (install) {
Package::the().install();
Package::the().install(targets);
}
if (pushOrStore) {
Package::the().store();
}
if (!addOrAur && !install && !pushOrStore) {
Package::the().list(targets);
if (!addOrAur && !install) {
Package::the().list(targets, pushOrSearch);
}
}
else if (helpOperation) {
// TODO open manpage
// TODO: open manpage
}
else {
// TODO open manpage
// TODO: open manpage
}
#ifndef NDEBUG
+28 -32
View File
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021-2022 Riyyi
* Copyright (C) 2021-2022,2025 Riyyi
*
* SPDX-License-Identifier: MIT
*/
@@ -14,11 +14,11 @@
#include <string> // getline
#include <vector>
#include "ruc/shell.h"
#include "ruc/system.h"
#include "machine.h"
#include "package.h"
#include "util/file.h"
#include "util/shell.h"
#include "util/system.h"
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;
}
void Package::install()
{
installOrAurInstall(InstallType::Install);
installOrAurInstall(InstallType::AurInstall, targets.size() != 0 ? targets.front() : PACKAGE_FILE);
}
void Package::list(const std::vector<std::string>& targets)
void Package::install(const std::vector<std::string>& targets)
{
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, bool partialMatch)
{
auto packagesOrEmpty = getPackageList();
@@ -62,7 +72,7 @@ void Package::list(const std::vector<std::string>& targets)
std::string line;
while (std::getline(stream, line)) {
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');
break;
}
@@ -72,20 +82,6 @@ void Package::list(const std::vector<std::string>& targets)
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()
@@ -105,7 +101,7 @@ std::optional<std::string> Package::fetchAurHelper()
return {};
}
void Package::installOrAurInstall(InstallType type)
void Package::installOrAurInstall(InstallType type, const std::string& file)
{
distroDetect();
distroDependencies();
@@ -126,15 +122,15 @@ void Package::installOrAurInstall(InstallType type)
std::string command = "";
Util::System $;
ruc::System $;
if (m_distro == Distro::Arch) {
// 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) {
// Determine which packages in the list are from the AUR
// NOTE: Util::System does not support commands with newlines
auto aurList = Util::Shell()("grep -vx '" + repoList.output() + "' ./packages");
// NOTE: ruc::System does not support commands with newlines
auto aurList = ruc::Shell()("grep -vx '" + repoList.output() + "' " + file);
command = aurHelper.value() + " -Sy --devel --needed --noconfirm " + aurList.output();
}
else {
@@ -143,7 +139,7 @@ void Package::installOrAurInstall(InstallType type)
}
else if (m_distro == Distro::Debian) {
// 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();
}
@@ -224,7 +220,7 @@ std::optional<std::string> Package::getPackageList()
std::string packages;
Util::System $;
ruc::System $;
if (m_distro == Distro::Arch) {
auto basePackages = $("pactree -u base").tail(2, true);
auto develPackages = $("pacman -Qqg base-devel");
+13 -14
View File
@@ -1,42 +1,43 @@
/*
* Copyright (C) 2021-2022 Riyyi
* Copyright (C) 2021-2022,2025 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#ifndef PACKAGE_H
#define PACKAGE_H
#pragma once
#include <cstdint> // uint8_t
#include <optional>
#include <string>
#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:
Package(s);
virtual ~Package();
enum class Distro {
enum class Distro : uint8_t {
Unsupported,
Arch,
Debian,
};
enum class InstallType {
enum class InstallType : uint8_t {
Install,
AurInstall,
};
void aurInstall();
void install();
void list(const std::vector<std::string>& targets = {});
void store();
void aurInstall(const std::vector<std::string>& targets = {});
void install(const std::vector<std::string>& targets = {});
void list(const std::vector<std::string>& targets = {}, bool partialMatch = false);
private:
std::optional<std::string> fetchAurHelper();
void installOrAurInstall(InstallType type);
void installOrAurInstall(InstallType type, const std::string& file);
bool findDependency(const std::string& search);
bool distroDetect();
@@ -45,5 +46,3 @@ private:
Distro m_distro { Distro::Unsupported };
};
#endif // PACKAGE_H
-567
View File
@@ -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
-116
View File
@@ -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
-89
View File
@@ -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
-38
View File
@@ -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
-53
View File
@@ -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
-30
View File
@@ -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
-50
View File
@@ -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
-328
View File
@@ -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
-65
View File
@@ -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
-88
View File
@@ -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
-45
View File
@@ -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
-79
View File
@@ -1,79 +0,0 @@
#ifndef TEST_H
#define TEST_H
#include <cstdio> // fprintf
#include <iostream> // cerr
#define GET_2TH_ARG(arg1, arg2, ...) arg2
#define GET_3TH_ARG(arg1, arg2, arg3, ...) arg3
#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4
#define MACRO_CHOOSER_1(macro, ...) \
GET_2TH_ARG(__VA_ARGS__, macro##_1, )
#define MACRO_CHOOSER_2(macro, ...) \
GET_3TH_ARG(__VA_ARGS__, macro##_2, macro##_1, )
#define MACRO_CHOOSER_3(macro, ...) \
GET_4TH_ARG(__VA_ARGS__, macro##_3, macro##_2, macro##_1, )
// -----------------------------------------
#define EXPECT_IMPL(x, result) \
if (!(x)) { \
fprintf(stderr, " \033[31;1mFAIL:\033[0m %s:%d: EXPECT(%s) failed\n", \
__FILE__, __LINE__, #x); \
Test::TestSuite::the().currentTestCaseFailed(); \
result; \
}
#define EXPECT_1(x) \
EXPECT_IMPL(x, (void)0)
#define EXPECT_2(x, result) \
EXPECT_IMPL(x, result)
#define EXPECT(...) \
MACRO_CHOOSER_2(EXPECT, __VA_ARGS__) \
(__VA_ARGS__)
// -----------------------------------------
#define EXPECT_EQ_IMPL(a, b, result) \
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(); \
result; \
}
#define EXPECT_EQ_2(a, b) \
EXPECT_EQ_IMPL(a, b, (void)0)
#define EXPECT_EQ_3(a, b, result) \
EXPECT_EQ_IMPL(a, b, result)
#define EXPECT_EQ(...) \
MACRO_CHOOSER_3(EXPECT_EQ, __VA_ARGS__) \
(__VA_ARGS__)
// -----------------------------------------
#define EXPECT_NE_IMPL(a, b, result) \
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(); \
result; \
}
#define EXPECT_NE_2(a, b) \
EXPECT_NE_IMPL(a, b, (void)0)
#define EXPECT_NE_3(a, b, result) \
EXPECT_NE_IMPL(a, b, result)
#define EXPECT_NE(...) \
MACRO_CHOOSER_3(EXPECT_NE, __VA_ARGS__) \
(__VA_ARGS__)
#endif // TEST_H
-8
View File
@@ -1,8 +0,0 @@
#include "testsuite.h"
int main(int, const char*[])
{
Test::TestSuite::the().run();
return 0;
}
-45
View File
@@ -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
-76
View File
@@ -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
-36
View File
@@ -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
+92 -20
View File
@@ -1,11 +1,10 @@
/*
* Copyright (C) 2022 Riyyi
* Copyright (C) 2022,2025 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#include <cstddef> // size_t
#include <cstdint> // uint32_t
#include <cstdio> // stderr
#include <filesystem> // path
#include <string>
@@ -13,14 +12,14 @@
#include <unordered_map>
#include <vector>
#include "ruc/file.h"
#include "config.h"
#include "dotfile.h"
#include "machine.h"
#include "macro.h"
#include "testcase.h"
#include "testsuite.h"
#include "util/file.h"
#include "util/system.h"
const bool root = !geteuid() ? true : false;
const std::filesystem::path homeDirectory = "/home/" + Machine::the().username();
@@ -43,7 +42,7 @@ void createTestDotfiles(const std::vector<std::string>& fileNames, const std::ve
std::filesystem::create_directories(directory);
}
auto file = Util::File::create(fileName);
auto file = ruc::File::create(fileName);
if (file.data().size() == 0) {
file.append(fileContents.at(i).c_str()).flush();
}
@@ -374,8 +373,8 @@ TEST_CASE(AddDotfiles)
EXPECT(std::filesystem::exists(file), continue);
EXPECT(std::filesystem::exists(file.substr(homeDirectorySize + 1)), continue);
Util::File lhs(file);
Util::File rhs(file.substr(homeDirectorySize + 1));
ruc::File lhs(file);
ruc::File rhs(file.substr(homeDirectorySize + 1));
EXPECT_EQ(lhs.data(), rhs.data());
}
@@ -384,9 +383,9 @@ TEST_CASE(AddDotfiles)
TEST_CASE(AddNonExistentDotfiles)
{
stderr = Test::TestSuite::the().outputNull();
stderr = test::TestSuite::the().outputNull();
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"));
removeTestDotfiles({ "__non-existent-test-file" });
@@ -430,8 +429,8 @@ TEST_CASE(PullDotfiles)
EXPECT(std::filesystem::exists(homeFileNames.at(i)), continue);
EXPECT(std::filesystem::exists(fileNames.at(i)), continue);
Util::File lhs(homeFileNames.at(i));
Util::File rhs(fileNames.at(i));
ruc::File lhs(homeFileNames.at(i));
ruc::File rhs(fileNames.at(i));
EXPECT_EQ(lhs.data(), rhs.data());
}
@@ -463,8 +462,8 @@ TEST_CASE(PushDotfiles)
EXPECT(std::filesystem::exists(file), continue);
EXPECT(std::filesystem::exists(homeDirectory / file), continue);
Util::File lhs(file);
Util::File rhs(homeDirectory / file);
ruc::File lhs(file);
ruc::File rhs((homeDirectory / file).string());
EXPECT_EQ(lhs.data(), rhs.data());
}
@@ -503,7 +502,7 @@ TEST_CASE(PushDotfilesWithIgnorePattern)
TEST_CASE(PushDotfilesSelectivelyComment)
{
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));
}
@@ -678,6 +677,43 @@ test data /**/ comment
/* 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 = {
@@ -846,6 +882,42 @@ 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);
@@ -857,7 +929,7 @@ test data /**/ uncomment
EXPECT(std::filesystem::exists(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));
}
@@ -889,8 +961,8 @@ TEST_CASE(PullSystemDotfiles)
Dotfile::the().pull({ "etc/group" });
Config::the().setSystemPatterns({});
Util::File lhs("/etc/group");
Util::File rhs(Config::the().workingDirectory() / "etc/group");
ruc::File lhs("/etc/group");
ruc::File rhs((Config::the().workingDirectory() / "etc/group").string());
EXPECT_EQ(lhs.data(), rhs.data());
std::filesystem::remove_all(Config::the().workingDirectory() / "etc");
@@ -909,7 +981,7 @@ TEST_CASE(AddSymlinkDotfiles)
EXPECT(std::filesystem::is_symlink(symlinkFileName));
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 });
}
@@ -928,7 +1000,7 @@ TEST_CASE(PullSymlinkDotfiles)
EXPECT(std::filesystem::is_symlink(symlinkFileName));
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 });
}
@@ -946,7 +1018,7 @@ TEST_CASE(PushSymlinkDotfiles)
EXPECT(std::filesystem::is_symlink(symlinkInHome));
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 });
}
-949
View File
@@ -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);
}
-39
View File
@@ -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);
}
-98
View File
@@ -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, "");
}
Vendored Submodule
+1
Submodule vendor/ruc added at 243442a3b6