Compare commits
21
Commits
448ad42005
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d8e4f3625 | ||
|
|
add2138321 | ||
|
|
05aff91858 | ||
|
|
e0ecd63dfd | ||
|
|
33ae6c57a2 | ||
|
|
07882506c0 | ||
|
|
9af4b778eb | ||
|
|
2f89d5cfdc | ||
|
|
0e15bfe4bf | ||
|
|
3d42a82003 | ||
|
|
4927e35bfc | ||
|
|
2b3acd5d51 | ||
|
|
6f2af1a40d | ||
|
|
6f0e3d6063 | ||
|
|
3e2819cf93 | ||
|
|
5e6ec1eff3 | ||
|
|
14e57acfd8 | ||
|
|
5cf8d18742 | ||
|
|
086da50a57 | ||
|
|
36a18f65c1 | ||
|
|
ea853c1ace |
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "vendor/ruc"]
|
||||||
|
path = vendor/ruc
|
||||||
|
url = https://github.com/Riyyi/ruc
|
||||||
+28
-18
@@ -4,6 +4,9 @@
|
|||||||
# Set project name
|
# Set project name
|
||||||
set(PROJECT "manafiles")
|
set(PROJECT "manafiles")
|
||||||
|
|
||||||
|
# Unit tests
|
||||||
|
option(MANAFILES_BUILD_TESTS "Build the Manafiles test programs" ON)
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
|
||||||
@@ -50,29 +53,23 @@ endif()
|
|||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Configure source
|
# Library
|
||||||
|
|
||||||
# Define include files
|
set(RUC_BUILD_TESTS OFF)
|
||||||
include_directories(
|
add_subdirectory("vendor/ruc")
|
||||||
"src"
|
|
||||||
"test"
|
# ------------------------------------------
|
||||||
)
|
# Application target
|
||||||
|
|
||||||
# Define source files
|
# Define source files
|
||||||
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
|
file(GLOB_RECURSE PROJECT_SOURCES "src/*.cpp")
|
||||||
set(PROJECT_SOURCES ${PROJECT_SOURCES})
|
set(PROJECT_SOURCES ${PROJECT_SOURCES})
|
||||||
|
|
||||||
# Define test source files
|
|
||||||
file(GLOB_RECURSE TEST_SOURCES "test/*.cpp")
|
|
||||||
file(GLOB_RECURSE MAIN_SOURCES "src/*.cpp")
|
|
||||||
set(TEST_SOURCES ${TEST_SOURCES} ${MAIN_SOURCES})
|
|
||||||
list(REMOVE_ITEM TEST_SOURCES "${CMAKE_SOURCE_DIR}/src/main.cpp")
|
|
||||||
|
|
||||||
# ------------------------------------------
|
|
||||||
# Application target
|
|
||||||
|
|
||||||
add_executable(${PROJECT} ${PROJECT_SOURCES})
|
add_executable(${PROJECT} ${PROJECT_SOURCES})
|
||||||
target_link_libraries(${PROJECT})
|
target_include_directories(${PROJECT} PRIVATE
|
||||||
|
"src"
|
||||||
|
"vendor/ruc/src")
|
||||||
|
target_link_libraries(${PROJECT} ruc)
|
||||||
|
|
||||||
install(TARGETS ${PROJECT}
|
install(TARGETS ${PROJECT}
|
||||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||||
@@ -88,8 +85,21 @@ add_dependencies(run ${PROJECT})
|
|||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Unit test target
|
# Unit test target
|
||||||
|
|
||||||
add_executable(test ${TEST_SOURCES})
|
if (MANAFILES_BUILD_TESTS)
|
||||||
target_link_libraries(test)
|
# Define test source files
|
||||||
|
file(GLOB_RECURSE TEST_SOURCES "test/*.cpp")
|
||||||
|
set(TEST_SOURCES ${TEST_SOURCES} ${PROJECT_SOURCES})
|
||||||
|
list(REMOVE_ITEM TEST_SOURCES "${CMAKE_SOURCE_DIR}/src/main.cpp")
|
||||||
|
|
||||||
|
add_executable(${PROJECT}-unit-test ${TEST_SOURCES})
|
||||||
|
target_include_directories(${PROJECT}-unit-test PRIVATE
|
||||||
|
"src"
|
||||||
|
"test"
|
||||||
|
"vendor/ruc/src"
|
||||||
|
"vendor/ruc/test")
|
||||||
|
target_link_libraries(${PROJECT}-unit-test ruc)
|
||||||
|
target_link_libraries(${PROJECT}-unit-test ruc-test)
|
||||||
|
endif()
|
||||||
|
|
||||||
# ------------------------------------------
|
# ------------------------------------------
|
||||||
# Man page target
|
# Man page target
|
||||||
|
|||||||
+49
-12
@@ -16,7 +16,7 @@ Config file and package tracking utility.
|
|||||||
|
|
||||||
If you are using *Arch Linux*, you can install the AUR package [[https://aur.archlinux.org/packages/manafiles-git/][manafiles-git]].
|
If you are using *Arch Linux*, you can install the AUR package [[https://aur.archlinux.org/packages/manafiles-git/][manafiles-git]].
|
||||||
|
|
||||||
Alternatively, you will have to [[#building][build from source]].
|
Alternatively, you will have to [[#compiling][build from source]].
|
||||||
|
|
||||||
*** Configuration
|
*** Configuration
|
||||||
|
|
||||||
@@ -86,21 +86,29 @@ or uncomments them depending on the machine's configuration. Such a block looks
|
|||||||
like this:
|
like this:
|
||||||
|
|
||||||
#+BEGIN_SRC shell-script
|
#+BEGIN_SRC shell-script
|
||||||
# >>> distro=arch hostname=arch-desktop user=anon
|
# >>> distro=arch hostname=arch-desktop user=anon session=xorg
|
||||||
export PATH="$PATH:$HOME/.local/bin"
|
export PATH="$PATH:$HOME/.local/bin"
|
||||||
# <<<
|
# <<<
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
This block uses all of the supported configurations, ~distro~, ~hostname~ and
|
This block uses all of the supported configurations, ~distro~, ~hostname~,
|
||||||
~user~. These can be used in any combination and order.
|
~user~ and ~session~. These can be used in any combination and order.
|
||||||
|
|
||||||
If *all* of the given configurations match, manafiles will make sure that the
|
If *all* of the given configurations match, manafiles will make sure that the
|
||||||
contents of the block are uncommented. If *any* of them do *not* match, then
|
contents of the block are uncommented. If *any* of them do *not* match, then
|
||||||
manafiles will make sure that the contents of the block are commented.
|
manafiles will make sure that the contents of the block are commented.
|
||||||
|
|
||||||
** Building
|
***** Usable values for the configurations
|
||||||
|
|
||||||
*** Build dependencies
|
| distro | session |
|
||||||
|
|--------+---------|
|
||||||
|
| arch | xorg |
|
||||||
|
| debian | wayland |
|
||||||
|
| ubuntu | |
|
||||||
|
|
||||||
|
** Compiling
|
||||||
|
|
||||||
|
*** Dependencies
|
||||||
|
|
||||||
- ~gcc-libs~
|
- ~gcc-libs~
|
||||||
- (make) ~cmake~
|
- (make) ~cmake~
|
||||||
@@ -112,17 +120,46 @@ manafiles will make sure that the contents of the block are commented.
|
|||||||
|
|
||||||
The optional requirements are for the package tracking functionality.
|
The optional requirements are for the package tracking functionality.
|
||||||
|
|
||||||
*** Build compilation
|
*** Building
|
||||||
|
|
||||||
|
To get the sources from the repository, run the following commands:
|
||||||
|
|
||||||
|
#+BEGIN_SRC shell-script
|
||||||
|
$ git clone https://github.com/riyyi/manafiles
|
||||||
|
$ cd manafiles
|
||||||
|
$ git submodule init
|
||||||
|
$ git submodule update
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
#+BEGIN_SRC shell-script
|
||||||
|
$ git clone --recursive https://github.com/riyyi/manafiles
|
||||||
|
$ cd manafiles
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
**** Compiling
|
||||||
|
|
||||||
|
To compile, run the following commands:
|
||||||
|
|
||||||
#+BEGIN_SRC shell-script
|
#+BEGIN_SRC shell-script
|
||||||
$ mkdir build
|
$ mkdir build
|
||||||
$ cd build
|
$ cd build
|
||||||
$ cmake ..
|
$ cmake ..
|
||||||
$ make
|
$ make
|
||||||
#+END_SRC
|
# Optional. This will install the manafiles executable in /usr/bin
|
||||||
|
|
||||||
*** Build installation
|
|
||||||
|
|
||||||
#+BEGIN_SRC shell-script
|
|
||||||
$ sudo make install
|
$ sudo make install
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
*** Uninstalling
|
||||||
|
|
||||||
|
To uninstall, run the following commands:
|
||||||
|
|
||||||
|
*Warning*: ~make uninstall~ will try to delete all files listed in the
|
||||||
|
~install_manifest.txt~ file inside your ~build~ folder. Make sure you first
|
||||||
|
check that it won't delete any important files.
|
||||||
|
|
||||||
|
#+BEGIN_SRC shell-script
|
||||||
|
$ cd build
|
||||||
|
$ sudo make uninstall
|
||||||
|
#+END_SRC
|
||||||
|
|||||||
+6
-4
@@ -1,4 +1,4 @@
|
|||||||
.TH MANAFILES 1 "2022-01-29" "manafiles 0.5" "User Commands"
|
.TH MANAFILES 1 "2025-02-02" "manafiles 0.6" "User Commands"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
manafiles \- config file and package tracking utility
|
manafiles \- config file and package tracking utility
|
||||||
@@ -31,7 +31,7 @@ See File Options below.
|
|||||||
.TP
|
.TP
|
||||||
.BR \-P ", " \-\-package
|
.BR \-P ", " \-\-package
|
||||||
Operate on packages. \
|
Operate on packages. \
|
||||||
This operation allows you to track installed packages and reinstall them. \
|
This operation allows tracking installed packages and reinstalling them. \
|
||||||
In the first case, if no package names are provided in the command line, all packages will be selected. \
|
In the first case, if no package names are provided in the command line, all packages will be selected. \
|
||||||
See Package Options below.
|
See Package Options below.
|
||||||
|
|
||||||
@@ -67,8 +67,10 @@ Install all AUR packages of the stored list.
|
|||||||
Install all official packages of the stored list.
|
Install all official packages of the stored list.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BR \-s ", " \-\-store
|
.BR \-s ", " \-\-search
|
||||||
Stores a list of all installed packages on the system.
|
Search each locally-installed package for names via a partial match. \
|
||||||
|
When including multiple search terms, all packages with names matching any of those terms are returned. \
|
||||||
|
If the \fB-s\fR option is ommited, packages names are filtered via a full match.
|
||||||
|
|
||||||
.SH EXAMPLES
|
.SH EXAMPLES
|
||||||
Usage examples:
|
Usage examples:
|
||||||
|
|||||||
+7
-7
@@ -12,8 +12,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "util/json/json.h"
|
#include "ruc/json/json.h"
|
||||||
#include "util/meta/assert.h"
|
#include "ruc/meta/assert.h"
|
||||||
|
|
||||||
Config::Config(s)
|
Config::Config(s)
|
||||||
: m_workingDirectory(std::filesystem::current_path())
|
: m_workingDirectory(std::filesystem::current_path())
|
||||||
@@ -51,7 +51,7 @@ void Config::parseConfigFile()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Util::Json json;
|
ruc::Json json;
|
||||||
|
|
||||||
std::ifstream file(m_config);
|
std::ifstream file(m_config);
|
||||||
if (!file.is_open()) {
|
if (!file.is_open()) {
|
||||||
@@ -72,17 +72,17 @@ void Config::parseConfigFile()
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
void toJson(Util::Json& json, const Settings& settings)
|
void toJson(ruc::Json& json, const Settings& settings)
|
||||||
{
|
{
|
||||||
json = Util::Json {
|
json = ruc::Json {
|
||||||
{ "ignorePatterns", settings.ignorePatterns },
|
{ "ignorePatterns", settings.ignorePatterns },
|
||||||
{ "systemPatterns", settings.systemPatterns }
|
{ "systemPatterns", settings.systemPatterns }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void fromJson(const Util::Json& json, Settings& settings)
|
void fromJson(const ruc::Json& json, Settings& settings)
|
||||||
{
|
{
|
||||||
VERIFY(json.type() == Util::Json::Type::Object);
|
VERIFY(json.type() == ruc::Json::Type::Object);
|
||||||
|
|
||||||
if (json.exists("ignorePatterns")) {
|
if (json.exists("ignorePatterns")) {
|
||||||
json.at("ignorePatterns").getTo(settings.ignorePatterns);
|
json.at("ignorePatterns").getTo(settings.ignorePatterns);
|
||||||
|
|||||||
+5
-5
@@ -11,8 +11,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/json/json.h"
|
#include "ruc/json/json.h"
|
||||||
#include "util/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
std::vector<std::string> ignorePatterns {
|
std::vector<std::string> ignorePatterns {
|
||||||
@@ -31,7 +31,7 @@ struct Settings {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
class Config : public Util::Singleton<Config> {
|
class Config : public ruc::Singleton<Config> {
|
||||||
public:
|
public:
|
||||||
Config(s);
|
Config(s);
|
||||||
virtual ~Config();
|
virtual ~Config();
|
||||||
@@ -65,5 +65,5 @@ private:
|
|||||||
|
|
||||||
// Json arbitrary type conversion functions
|
// Json arbitrary type conversion functions
|
||||||
|
|
||||||
void toJson(Util::Json& object, const Settings& settings);
|
void toJson(ruc::Json& object, const Settings& settings);
|
||||||
void fromJson(const Util::Json& object, Settings& settings);
|
void fromJson(const ruc::Json& object, Settings& settings);
|
||||||
|
|||||||
+54
-20
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
* Copyright (C) 2021-2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -15,11 +15,12 @@
|
|||||||
#include <unistd.h> // geteuid, getlogin, setegid, seteuid
|
#include <unistd.h> // geteuid, getlogin, setegid, seteuid
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ruc/file.h"
|
||||||
|
#include "ruc/meta/assert.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "dotfile.h"
|
#include "dotfile.h"
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/meta/assert.h"
|
|
||||||
|
|
||||||
Dotfile::Dotfile(s)
|
Dotfile::Dotfile(s)
|
||||||
{
|
{
|
||||||
@@ -114,7 +115,12 @@ bool Dotfile::match(const std::string& path, const std::vector<std::string>& pat
|
|||||||
std::string pathString = path.substr(cutFrom);
|
std::string pathString = path.substr(cutFrom);
|
||||||
|
|
||||||
for (const auto& pattern : patterns) {
|
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) {
|
if (pathString == pattern) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -373,41 +379,60 @@ void Dotfile::sync(SyncType type,
|
|||||||
|
|
||||||
void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
||||||
{
|
{
|
||||||
Util::File dotfile(path);
|
ruc::File dotfile(path);
|
||||||
|
|
||||||
const std::string search[3] = {
|
const std::string search[4] = {
|
||||||
"distro=",
|
"distro=",
|
||||||
"hostname=",
|
"hostname=",
|
||||||
"user=",
|
"user=",
|
||||||
|
"session=",
|
||||||
};
|
};
|
||||||
|
|
||||||
// State of the loop
|
// State of the loop
|
||||||
bool isFiltering = false;
|
bool isFiltering = false;
|
||||||
std::string filter[3];
|
std::string filter[4];
|
||||||
std::string commentCharacter;
|
std::string commentCharacter;
|
||||||
std::string commentTerminationCharacter;
|
std::string commentTerminationCharacter;
|
||||||
size_t positionInFile = 0;
|
size_t positionInFile = 0;
|
||||||
|
|
||||||
auto commentOrUncommentLine = [&](std::string& line, bool addComment) {
|
auto commentOrUncommentLine = [&](std::string& line, bool addComment) {
|
||||||
|
size_t indentation = line.find_first_not_of(" \t");
|
||||||
|
|
||||||
|
// Empty lines are skipped
|
||||||
|
if (line.empty() || indentation == std::string::npos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
size_t lineLength = line.size();
|
size_t lineLength = line.size();
|
||||||
size_t whiteSpaceBeforeComment = line.find_first_not_of(" \t");
|
size_t commentStart = line.find(commentCharacter, indentation);
|
||||||
size_t contentAfterComment = line.find_first_not_of(" \t" + commentCharacter);
|
size_t commentEnd = line.rfind(commentTerminationCharacter);
|
||||||
// NOTE: The +1 is needed to take the newline into account
|
bool hasComment = commentStart != std::string::npos && (commentTerminationCharacter.empty() || commentEnd != std::string::npos);
|
||||||
size_t contentLength = lineLength + 1 - contentAfterComment - (lineLength - line.find_last_not_of(" \t" + commentTerminationCharacter));
|
|
||||||
|
|
||||||
// If there was no comment, grab whitespace correctly
|
// Lines that have a comment at the *end* of the line aren't considered commented lines
|
||||||
if (whiteSpaceBeforeComment == std::string::npos) {
|
if (hasComment && line.find_first_not_of(" \t") < commentStart) {
|
||||||
whiteSpaceBeforeComment = contentAfterComment;
|
hasComment = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!addComment) {
|
// Uncomment line
|
||||||
line = line.substr(0, whiteSpaceBeforeComment)
|
if (hasComment && !addComment) {
|
||||||
+ line.substr(contentAfterComment, contentLength);
|
size_t contentStart = line.find_first_not_of(" \t", commentStart + commentCharacter.size());
|
||||||
|
size_t contentLength = commentEnd - contentStart;
|
||||||
|
|
||||||
|
// Trim trailing whitespace
|
||||||
|
std::string content = line.substr(contentStart, contentLength);
|
||||||
|
content = content.substr(0, content.find_last_not_of(" \t") + 1);
|
||||||
|
|
||||||
|
line = line.substr(0, indentation)
|
||||||
|
+ content;
|
||||||
}
|
}
|
||||||
else {
|
// Comment line
|
||||||
line = line.substr(0, whiteSpaceBeforeComment)
|
else if (!hasComment && addComment) {
|
||||||
|
size_t contentStart = line.find_first_not_of(" \t");
|
||||||
|
size_t contentLength = line.size() - contentStart;
|
||||||
|
|
||||||
|
line = line.substr(0, indentation)
|
||||||
+ commentCharacter + ' '
|
+ commentCharacter + ' '
|
||||||
+ line.substr(contentAfterComment, contentLength)
|
+ line.substr(contentStart, contentLength)
|
||||||
+ (!commentTerminationCharacter.empty() ? ' ' + commentTerminationCharacter : "");
|
+ (!commentTerminationCharacter.empty() ? ' ' + commentTerminationCharacter : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,7 +445,7 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
|||||||
if (line.find(">>>") != std::string::npos) {
|
if (line.find(">>>") != std::string::npos) {
|
||||||
// Find machine info
|
// Find machine info
|
||||||
size_t find = 0;
|
size_t find = 0;
|
||||||
for (size_t i = 0; i < 3; ++i) {
|
for (size_t i = 0; i < 4; ++i) {
|
||||||
find = line.find(search[i]) + search[i].size();
|
find = line.find(search[i]) + search[i].size();
|
||||||
if (find < search[i].size()) {
|
if (find < search[i].size()) {
|
||||||
continue;
|
continue;
|
||||||
@@ -435,6 +460,10 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
|||||||
if (i > 0 && commentCharacter.at(i - 1) == '/' && commentCharacter.at(i) == '*') {
|
if (i > 0 && commentCharacter.at(i - 1) == '/' && commentCharacter.at(i) == '*') {
|
||||||
commentTerminationCharacter = "*/";
|
commentTerminationCharacter = "*/";
|
||||||
}
|
}
|
||||||
|
// Support for <!-- XMl comments -->
|
||||||
|
if (i > 0 && commentCharacter.at(i - 1) == '<' && commentCharacter.at(i) == '!' && commentCharacter.at(i + 1) == '-' && commentCharacter.at(i + 2) == '-') {
|
||||||
|
commentTerminationCharacter = "-->";
|
||||||
|
}
|
||||||
// NOTE: Modification of the string should be at the end of the iteration to prevent 'out of range' errors
|
// NOTE: Modification of the string should be at the end of the iteration to prevent 'out of range' errors
|
||||||
if (commentCharacter.at(i) == ' ' || commentCharacter.at(i) == '\t') {
|
if (commentCharacter.at(i) == ' ' || commentCharacter.at(i) == '\t') {
|
||||||
commentCharacter.erase(i, 1);
|
commentCharacter.erase(i, 1);
|
||||||
@@ -450,6 +479,7 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
|||||||
filter[0] = "";
|
filter[0] = "";
|
||||||
filter[1] = "";
|
filter[1] = "";
|
||||||
filter[2] = "";
|
filter[2] = "";
|
||||||
|
filter[3] = "";
|
||||||
commentCharacter.clear();
|
commentCharacter.clear();
|
||||||
commentTerminationCharacter.clear();
|
commentTerminationCharacter.clear();
|
||||||
continue;
|
continue;
|
||||||
@@ -471,6 +501,10 @@ void Dotfile::selectivelyCommentOrUncomment(const std::string& path)
|
|||||||
commentOrUncommentLine(line, true);
|
commentOrUncommentLine(line, true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
else if (filter[3] != Machine::the().session() && !filter[3].empty()) {
|
||||||
|
commentOrUncommentLine(line, true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
commentOrUncommentLine(line, false);
|
commentOrUncommentLine(line, false);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -13,9 +13,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
|
|
||||||
class Dotfile : public Util::Singleton<Dotfile> {
|
class Dotfile : public ruc::Singleton<Dotfile> {
|
||||||
public:
|
public:
|
||||||
Dotfile(s);
|
Dotfile(s);
|
||||||
virtual ~Dotfile();
|
virtual ~Dotfile();
|
||||||
|
|||||||
+90
-6
@@ -1,21 +1,25 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <pwd.h> // getpwnam
|
#include <cstdint> // int8_t
|
||||||
#include <sstream> // istringstream
|
#include <filesystem> // std::filesystem::path
|
||||||
#include <unistd.h> // gethostname, getlogin
|
#include <pwd.h> // getpwnam
|
||||||
|
#include <sstream> // istringstream
|
||||||
|
#include <unistd.h> // gethostname, getlogin
|
||||||
|
|
||||||
|
#include "ruc/file.h"
|
||||||
|
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
#include "util/file.h"
|
|
||||||
|
|
||||||
Machine::Machine(s)
|
Machine::Machine(s)
|
||||||
{
|
{
|
||||||
fetchDistro();
|
fetchDistro();
|
||||||
fetchHostname();
|
fetchHostname();
|
||||||
fetchUsername();
|
fetchUsername();
|
||||||
|
fetchSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
Machine::~Machine()
|
Machine::~Machine()
|
||||||
@@ -26,7 +30,7 @@ Machine::~Machine()
|
|||||||
|
|
||||||
void Machine::fetchDistro()
|
void Machine::fetchDistro()
|
||||||
{
|
{
|
||||||
Util::File osRelease("/etc/os-release");
|
ruc::File osRelease("/etc/os-release");
|
||||||
std::istringstream stream(osRelease.data());
|
std::istringstream stream(osRelease.data());
|
||||||
for (std::string line; std::getline(stream, line);) {
|
for (std::string line; std::getline(stream, line);) {
|
||||||
if (line.find("ID=") == 0) {
|
if (line.find("ID=") == 0) {
|
||||||
@@ -61,3 +65,83 @@ void Machine::fetchUsername()
|
|||||||
perror("\033[31;1mError:\033[0m getpwnam");
|
perror("\033[31;1mError:\033[0m getpwnam");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Machine::fetchSession()
|
||||||
|
{
|
||||||
|
// Determine if this is an Xorg or Wayland session
|
||||||
|
int8_t likelyWayland = 0;
|
||||||
|
|
||||||
|
// Detect via environment variable
|
||||||
|
const char* env;
|
||||||
|
env = std::getenv("XDG_SESSION_TYPE");
|
||||||
|
if (env != nullptr) {
|
||||||
|
auto session = std::string(env);
|
||||||
|
if (session == "wayland") {
|
||||||
|
likelyWayland++;
|
||||||
|
}
|
||||||
|
else if (session == "x11") {
|
||||||
|
likelyWayland--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
env = std::getenv("WAYLAND_DISPLAY");
|
||||||
|
if (env != nullptr) {
|
||||||
|
auto display = std::string(env);
|
||||||
|
if (display.find("wayland-", 0) == 0) {
|
||||||
|
likelyWayland++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect via Wayland socket
|
||||||
|
auto socket = std::filesystem::path("/run/user") / std::to_string(uid());
|
||||||
|
if (std::filesystem::exists(socket) && std::filesystem::is_directory(socket)) {
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(socket)) {
|
||||||
|
if (entry.path().filename().string().find("wayland-", 0) == 0) {
|
||||||
|
likelyWayland++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect via Xorg socket
|
||||||
|
if (std::filesystem::exists("/tmp/.X11-unix")) {
|
||||||
|
likelyWayland--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect via running processes, /proc/<id>/comm
|
||||||
|
std::filesystem::path processes = "/proc";
|
||||||
|
if (std::filesystem::exists(processes) && std::filesystem::is_directory(processes)) {
|
||||||
|
for (const auto& entry : std::filesystem::directory_iterator(processes)) {
|
||||||
|
if (std::filesystem::is_directory(entry)) {
|
||||||
|
|
||||||
|
std::filesystem::path comm = entry.path() / "comm";
|
||||||
|
if (!std::filesystem::exists(comm)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the contents of the "comm" file
|
||||||
|
std::ifstream stream(comm);
|
||||||
|
std::string command;
|
||||||
|
std::getline(stream, command);
|
||||||
|
|
||||||
|
if (command == "Xwayland" || command == "sway" || command == "hyprland") {
|
||||||
|
likelyWayland++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command == "Xorg" || command == "xinit" || command == "i3" || command == "bspwm") {
|
||||||
|
likelyWayland--;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we detected at least 2 ways, we can be fairly certain
|
||||||
|
m_session = "";
|
||||||
|
if (likelyWayland <= -2) {
|
||||||
|
m_session = "xorg";
|
||||||
|
}
|
||||||
|
else if (likelyWayland >= 2) {
|
||||||
|
m_session = "wayland";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+12
-8
@@ -10,28 +10,32 @@
|
|||||||
#include <pwd.h> // passwd
|
#include <pwd.h> // passwd
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "util/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
|
|
||||||
class Machine : public Util::Singleton<Machine> {
|
class Machine : public ruc::Singleton<Machine> {
|
||||||
public:
|
public:
|
||||||
Machine(s);
|
Machine(s);
|
||||||
virtual ~Machine();
|
virtual ~Machine();
|
||||||
|
|
||||||
const std::string& distroId() { return m_distroId; }
|
const std::string& distroId() const { return m_distroId; }
|
||||||
const std::string& distroIdLike() { return m_distroIdLike; }
|
const std::string& distroIdLike() const { return m_distroIdLike; }
|
||||||
const std::string& hostname() { return m_hostname; }
|
const std::string& hostname() const { return m_hostname; }
|
||||||
|
|
||||||
std::string username() { return m_passwd->pw_name; }
|
std::string username() const { return std::string(m_passwd->pw_name); }
|
||||||
uint32_t uid() { return m_passwd->pw_uid; }
|
uint32_t uid() const { return m_passwd->pw_uid; }
|
||||||
uint32_t gid() { return m_passwd->pw_gid; }
|
uint32_t gid() const { return m_passwd->pw_gid; }
|
||||||
|
|
||||||
|
const std::string& session() const { return m_session; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void fetchDistro();
|
void fetchDistro();
|
||||||
void fetchHostname();
|
void fetchHostname();
|
||||||
void fetchUsername();
|
void fetchUsername();
|
||||||
|
void fetchSession();
|
||||||
|
|
||||||
std::string m_distroId;
|
std::string m_distroId;
|
||||||
std::string m_distroIdLike;
|
std::string m_distroIdLike;
|
||||||
std::string m_hostname;
|
std::string m_hostname;
|
||||||
|
std::string m_session;
|
||||||
passwd* m_passwd { nullptr };
|
passwd* m_passwd { nullptr };
|
||||||
};
|
};
|
||||||
|
|||||||
+18
-20
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
* Copyright (C) 2021-2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -8,11 +8,12 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ruc/argparser.h"
|
||||||
|
#include "ruc/timer.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "dotfile.h"
|
#include "dotfile.h"
|
||||||
#include "package.h"
|
#include "package.h"
|
||||||
#include "util/argparser.h"
|
|
||||||
#include "util/timer.h"
|
|
||||||
|
|
||||||
int main(int argc, const char* argv[])
|
int main(int argc, const char* argv[])
|
||||||
{
|
{
|
||||||
@@ -23,12 +24,12 @@ int main(int argc, const char* argv[])
|
|||||||
bool addOrAur = false;
|
bool addOrAur = false;
|
||||||
bool install = false;
|
bool install = false;
|
||||||
bool pull = false;
|
bool pull = false;
|
||||||
bool pushOrStore = false;
|
bool pushOrSearch = false;
|
||||||
bool verbose = false;
|
bool verbose = false;
|
||||||
|
|
||||||
std::vector<std::string> targets {};
|
std::vector<std::string> targets {};
|
||||||
|
|
||||||
Util::ArgParser argParser;
|
ruc::ArgParser argParser;
|
||||||
argParser.addOption(fileOperation, 'F', "file", nullptr, nullptr);
|
argParser.addOption(fileOperation, 'F', "file", nullptr, nullptr);
|
||||||
argParser.addOption(packageOperation, 'P', "package", nullptr, nullptr);
|
argParser.addOption(packageOperation, 'P', "package", nullptr, nullptr);
|
||||||
argParser.addOption(helpOperation, 'h', "help", nullptr, nullptr);
|
argParser.addOption(helpOperation, 'h', "help", nullptr, nullptr);
|
||||||
@@ -36,10 +37,10 @@ int main(int argc, const char* argv[])
|
|||||||
argParser.addOption(addOrAur, 'a', "add", nullptr, nullptr);
|
argParser.addOption(addOrAur, 'a', "add", nullptr, nullptr);
|
||||||
argParser.addOption(install, 'i', "install", nullptr, nullptr);
|
argParser.addOption(install, 'i', "install", nullptr, nullptr);
|
||||||
argParser.addOption(pull, 'l', "pull", nullptr, nullptr);
|
argParser.addOption(pull, 'l', "pull", nullptr, nullptr);
|
||||||
argParser.addOption(pushOrStore, 's', "push", nullptr, nullptr);
|
argParser.addOption(pushOrSearch, 's', "push", nullptr, nullptr);
|
||||||
argParser.addOption(verbose, 'v', "verbose", nullptr, nullptr);
|
argParser.addOption(verbose, 'v', "verbose", nullptr, nullptr);
|
||||||
|
|
||||||
argParser.addArgument(targets, "targets", nullptr, nullptr, Util::ArgParser::Required::No);
|
argParser.addArgument(targets, "targets", nullptr, nullptr, ruc::ArgParser::Required::No);
|
||||||
argParser.parse(argc, argv);
|
argParser.parse(argc, argv);
|
||||||
|
|
||||||
if (fileOperation + packageOperation + helpOperation > 1) {
|
if (fileOperation + packageOperation + helpOperation > 1) {
|
||||||
@@ -48,7 +49,7 @@ int main(int argc, const char* argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
Util::Timer t;
|
ruc::Timer t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Config::the().setVerbose(verbose);
|
Config::the().setVerbose(verbose);
|
||||||
@@ -60,36 +61,33 @@ int main(int argc, const char* argv[])
|
|||||||
if (pull) {
|
if (pull) {
|
||||||
Dotfile::the().pull(targets);
|
Dotfile::the().pull(targets);
|
||||||
}
|
}
|
||||||
if (pushOrStore) {
|
if (pushOrSearch) {
|
||||||
Dotfile::the().push(targets);
|
Dotfile::the().push(targets);
|
||||||
}
|
}
|
||||||
if (!addOrAur && !pull && !pushOrStore) {
|
if (!addOrAur && !pull && !pushOrSearch) {
|
||||||
Dotfile::the().list(targets);
|
Dotfile::the().list(targets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (packageOperation) {
|
else if (packageOperation) {
|
||||||
if (addOrAur) {
|
if (addOrAur) {
|
||||||
Package::the().aurInstall();
|
Package::the().aurInstall(targets);
|
||||||
}
|
}
|
||||||
if (install) {
|
if (install) {
|
||||||
Package::the().install();
|
Package::the().install(targets);
|
||||||
}
|
}
|
||||||
if (pushOrStore) {
|
if (!addOrAur && !install) {
|
||||||
Package::the().store();
|
Package::the().list(targets, pushOrSearch);
|
||||||
}
|
|
||||||
if (!addOrAur && !install && !pushOrStore) {
|
|
||||||
Package::the().list(targets);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (helpOperation) {
|
else if (helpOperation) {
|
||||||
// TODO open manpage
|
// TODO: open manpage
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO open manpage
|
// TODO: open manpage
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
printf("%fms\n", t.elapsedNanoseconds() / 1000000.0);
|
printf("%fms\n", t.elapsedNanoseconds() / 1000000.0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+27
-31
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
* Copyright (C) 2021-2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -14,11 +14,11 @@
|
|||||||
#include <string> // getline
|
#include <string> // getline
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ruc/shell.h"
|
||||||
|
#include "ruc/system.h"
|
||||||
|
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
#include "package.h"
|
#include "package.h"
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/shell.h"
|
|
||||||
#include "util/system.h"
|
|
||||||
|
|
||||||
Package::Package(s)
|
Package::Package(s)
|
||||||
{
|
{
|
||||||
@@ -30,17 +30,27 @@ Package::~Package()
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
void Package::aurInstall()
|
void Package::aurInstall(const std::vector<std::string>& targets)
|
||||||
{
|
{
|
||||||
installOrAurInstall(InstallType::AurInstall);
|
if (targets.size() > 1) {
|
||||||
|
fprintf(stderr, "\033[31;1mPackage:\033[0m only 1 file can be read packages from at a time\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
installOrAurInstall(InstallType::AurInstall, targets.size() != 0 ? targets.front() : PACKAGE_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Package::install()
|
void Package::install(const std::vector<std::string>& targets)
|
||||||
{
|
{
|
||||||
installOrAurInstall(InstallType::Install);
|
if (targets.size() > 1) {
|
||||||
|
fprintf(stderr, "\033[31;1mPackage:\033[0m only 1 file can be read packages from at a time\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
installOrAurInstall(InstallType::Install, targets.size() != 0 ? targets.front() : PACKAGE_FILE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Package::list(const std::vector<std::string>& targets)
|
void Package::list(const std::vector<std::string>& targets, bool partialMatch)
|
||||||
{
|
{
|
||||||
auto packagesOrEmpty = getPackageList();
|
auto packagesOrEmpty = getPackageList();
|
||||||
|
|
||||||
@@ -62,7 +72,7 @@ void Package::list(const std::vector<std::string>& targets)
|
|||||||
std::string line;
|
std::string line;
|
||||||
while (std::getline(stream, line)) {
|
while (std::getline(stream, line)) {
|
||||||
for (const auto& target : targets) {
|
for (const auto& target : targets) {
|
||||||
if (line.find(target) != std::string::npos) {
|
if (line == target || (partialMatch && line.find(target) != std::string::npos)) {
|
||||||
packages.append(line + '\n');
|
packages.append(line + '\n');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -72,20 +82,6 @@ void Package::list(const std::vector<std::string>& targets)
|
|||||||
printf("%s", packages.c_str());
|
printf("%s", packages.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Package::store()
|
|
||||||
{
|
|
||||||
auto packagesOrEmpty = getPackageList();
|
|
||||||
|
|
||||||
if (!packagesOrEmpty.has_value()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto packageFile = Util::File::create("./packages");
|
|
||||||
packageFile.clear();
|
|
||||||
packageFile.append(packagesOrEmpty.value());
|
|
||||||
packageFile.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
std::optional<std::string> Package::fetchAurHelper()
|
std::optional<std::string> Package::fetchAurHelper()
|
||||||
@@ -105,7 +101,7 @@ std::optional<std::string> Package::fetchAurHelper()
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Package::installOrAurInstall(InstallType type)
|
void Package::installOrAurInstall(InstallType type, const std::string& file)
|
||||||
{
|
{
|
||||||
distroDetect();
|
distroDetect();
|
||||||
distroDependencies();
|
distroDependencies();
|
||||||
@@ -126,15 +122,15 @@ void Package::installOrAurInstall(InstallType type)
|
|||||||
|
|
||||||
std::string command = "";
|
std::string command = "";
|
||||||
|
|
||||||
Util::System $;
|
ruc::System $;
|
||||||
if (m_distro == Distro::Arch) {
|
if (m_distro == Distro::Arch) {
|
||||||
// Grab everything off enabled official repositories that is in the list
|
// Grab everything off enabled official repositories that is in the list
|
||||||
auto repoList = $("pacman -Ssq") | $("grep -xf ./packages");
|
auto repoList = $("pacman -Ssq") | $("grep -xf " + file);
|
||||||
|
|
||||||
if (type == InstallType::AurInstall) {
|
if (type == InstallType::AurInstall) {
|
||||||
// Determine which packages in the list are from the AUR
|
// Determine which packages in the list are from the AUR
|
||||||
// NOTE: Util::System does not support commands with newlines
|
// NOTE: ruc::System does not support commands with newlines
|
||||||
auto aurList = Util::Shell()("grep -vx '" + repoList.output() + "' ./packages");
|
auto aurList = ruc::Shell()("grep -vx '" + repoList.output() + "' " + file);
|
||||||
command = aurHelper.value() + " -Sy --devel --needed --noconfirm " + aurList.output();
|
command = aurHelper.value() + " -Sy --devel --needed --noconfirm " + aurList.output();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -143,7 +139,7 @@ void Package::installOrAurInstall(InstallType type)
|
|||||||
}
|
}
|
||||||
else if (m_distro == Distro::Debian) {
|
else if (m_distro == Distro::Debian) {
|
||||||
// Grab everything off enabled official repositories that is in the list
|
// Grab everything off enabled official repositories that is in the list
|
||||||
auto repoList = $("apt-cache search .").cut(1, ' ') | $("grep -xf ./packages");
|
auto repoList = $("apt-cache search .").cut(1, ' ') | $("grep -xf " + file);
|
||||||
command = "apt install " + repoList.output();
|
command = "apt install " + repoList.output();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +220,7 @@ std::optional<std::string> Package::getPackageList()
|
|||||||
|
|
||||||
std::string packages;
|
std::string packages;
|
||||||
|
|
||||||
Util::System $;
|
ruc::System $;
|
||||||
if (m_distro == Distro::Arch) {
|
if (m_distro == Distro::Arch) {
|
||||||
auto basePackages = $("pactree -u base").tail(2, true);
|
auto basePackages = $("pactree -u base").tail(2, true);
|
||||||
auto develPackages = $("pacman -Qqg base-devel");
|
auto develPackages = $("pacman -Qqg base-devel");
|
||||||
|
|||||||
+9
-8
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
* Copyright (C) 2021-2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -11,9 +11,11 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "util/singleton.h"
|
#include "ruc/singleton.h"
|
||||||
|
|
||||||
class Package : public Util::Singleton<Package> {
|
#define PACKAGE_FILE "./packages"
|
||||||
|
|
||||||
|
class Package : public ruc::Singleton<Package> {
|
||||||
public:
|
public:
|
||||||
Package(s);
|
Package(s);
|
||||||
virtual ~Package();
|
virtual ~Package();
|
||||||
@@ -29,14 +31,13 @@ public:
|
|||||||
AurInstall,
|
AurInstall,
|
||||||
};
|
};
|
||||||
|
|
||||||
void aurInstall();
|
void aurInstall(const std::vector<std::string>& targets = {});
|
||||||
void install();
|
void install(const std::vector<std::string>& targets = {});
|
||||||
void list(const std::vector<std::string>& targets = {});
|
void list(const std::vector<std::string>& targets = {}, bool partialMatch = false);
|
||||||
void store();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::optional<std::string> fetchAurHelper();
|
std::optional<std::string> fetchAurHelper();
|
||||||
void installOrAurInstall(InstallType type);
|
void installOrAurInstall(InstallType type, const std::string& file);
|
||||||
|
|
||||||
bool findDependency(const std::string& search);
|
bool findDependency(const std::string& search);
|
||||||
bool distroDetect();
|
bool distroDetect();
|
||||||
|
|||||||
@@ -1,568 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm> // find_if
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint8_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 : uint8_t {
|
|
||||||
No,
|
|
||||||
DirectlyAfter,
|
|
||||||
Seperated,
|
|
||||||
};
|
|
||||||
|
|
||||||
auto argument = ArgumentProvided::No;
|
|
||||||
if (name != value || option.find('=') != std::string_view::npos) {
|
|
||||||
argument = ArgumentProvided::DirectlyAfter;
|
|
||||||
}
|
|
||||||
else if (!next.empty() && next[0] != '-') {
|
|
||||||
argument = ArgumentProvided::Seperated;
|
|
||||||
value = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool result = true;
|
|
||||||
|
|
||||||
if (foundOption->requiresArgument == Required::No) {
|
|
||||||
if (argument == ArgumentProvided::DirectlyAfter) {
|
|
||||||
foundOption->error = Error::OptionDoesntAllowArgument;
|
|
||||||
printError(name.data(), Error::OptionDoesntAllowArgument);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = foundOption->acceptValue("");
|
|
||||||
}
|
|
||||||
else if (foundOption->requiresArgument == Required::Yes) {
|
|
||||||
if (argument == ArgumentProvided::No) {
|
|
||||||
foundOption->error = Error::OptionRequiresArgument;
|
|
||||||
printError(name.data(), Error::OptionRequiresArgument);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = foundOption->acceptValue(value.data());
|
|
||||||
if (!result) {
|
|
||||||
printError(name.data(), Error::OptionInvalidArgumentType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argument == ArgumentProvided::Seperated) {
|
|
||||||
m_optionIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (foundOption->requiresArgument == Required::Optional) {
|
|
||||||
if (argument == ArgumentProvided::DirectlyAfter) {
|
|
||||||
result = foundOption->acceptValue(value.data());
|
|
||||||
if (!result) {
|
|
||||||
printError(name.data(), Error::OptionInvalidArgumentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ArgParser::parseArgument(std::string_view argument)
|
|
||||||
{
|
|
||||||
bool result = true;
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
// Run out of argument handlers
|
|
||||||
if (m_argumentIndex >= m_arguments.size()) {
|
|
||||||
printError(argument.data(), Error::ArgumentExtraOperand);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Argument& currentArgument = m_arguments.at(m_argumentIndex);
|
|
||||||
result = currentArgument.acceptValue(argument.data());
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
currentArgument.addedValues++;
|
|
||||||
if (currentArgument.addedValues >= currentArgument.maxValues) {
|
|
||||||
m_argumentIndex++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (currentArgument.minValues == 0) {
|
|
||||||
m_argumentIndex++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printError(argument.data(), Error::ArgumentInvalidType);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ArgParser::parse(int argc, const char* argv[])
|
|
||||||
{
|
|
||||||
bool result = true;
|
|
||||||
|
|
||||||
// Set looping indices
|
|
||||||
m_optionIndex = 1;
|
|
||||||
m_argumentIndex = 0;
|
|
||||||
|
|
||||||
// By default parse all '-' prefixed parameters as options
|
|
||||||
m_nonOptionMode = false;
|
|
||||||
|
|
||||||
// Get program name
|
|
||||||
m_name = argv[0] + std::string_view(argv[0]).find_last_of('/') + 1;
|
|
||||||
|
|
||||||
std::string_view argument;
|
|
||||||
std::string_view next;
|
|
||||||
for (; m_optionIndex < (size_t)argc; ++m_optionIndex) {
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
printf("argv[%zu]: %s\n", m_optionIndex, argv[m_optionIndex]);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Get the current and next parameter
|
|
||||||
argument = argv[m_optionIndex];
|
|
||||||
if (m_optionIndex + 1 < (size_t)argc && argv[m_optionIndex + 1][0] != '-') {
|
|
||||||
next = argv[m_optionIndex + 1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
next = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop parsing '-' prefixed parameters as options afer '--'
|
|
||||||
if (argument.compare("--") == 0) {
|
|
||||||
m_nonOptionMode = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Long Option
|
|
||||||
if (!m_nonOptionMode && argument[0] == '-' && argument[1] == '-') {
|
|
||||||
argument = argument.substr(argument.find_first_not_of('-'));
|
|
||||||
if (!parseLongOption(argument, next)) {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Short Option
|
|
||||||
else if (!m_nonOptionMode && argument[0] == '-') {
|
|
||||||
argument = argument.substr(argument.find_first_not_of('-'));
|
|
||||||
if (!parseShortOption(argument, next)) {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Argument
|
|
||||||
else {
|
|
||||||
if (m_stopParsingOnFirstNonOption) {
|
|
||||||
m_nonOptionMode = true;
|
|
||||||
}
|
|
||||||
if (!parseArgument(argument)) {
|
|
||||||
result = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_exitOnFirstError && !result) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check any leftover arguments for required
|
|
||||||
for (; m_argumentIndex < m_arguments.size(); ++m_argumentIndex) {
|
|
||||||
Argument& currentArgument = m_arguments.at(m_argumentIndex);
|
|
||||||
if (currentArgument.minValues > currentArgument.addedValues) {
|
|
||||||
result = false;
|
|
||||||
printError(currentArgument.name ? currentArgument.name : "", Error::ArgumentRequired);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto& option : m_options) {
|
|
||||||
if (option.longName && strcmp(option.longName, "help") == 0) {
|
|
||||||
printf("Try '%s --help' for more information.\n", m_name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (option.shortName == 'h') {
|
|
||||||
printf("Try '%s -h' for more information.\n", m_name);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void ArgParser::addOption(Option&& option)
|
|
||||||
{
|
|
||||||
m_options.push_back(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, nullptr, usageString, manString, Required::No, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(const char*& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(std::string& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(std::string_view& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(unsigned int& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(double& value, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addOption(std::vector<std::string>& values, char shortName, const char* longName, const char* usageString, const char* manString, const char* argumentName, Required requiresArgument)
|
|
||||||
{
|
|
||||||
addOption({ shortName, longName, argumentName, usageString, manString, requiresArgument, getAcceptFunction(values) });
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void ArgParser::addArgument(Argument&& argument)
|
|
||||||
{
|
|
||||||
m_arguments.push_back(argument);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(bool& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(const char*& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(std::string& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(std::string_view& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(int& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(unsigned int& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(double& value, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, 1, 0, getAcceptFunction(value) });
|
|
||||||
}
|
|
||||||
|
|
||||||
void ArgParser::addArgument(std::vector<std::string>& values, const char* name, const char* usageString, const char* manString, Required required)
|
|
||||||
{
|
|
||||||
size_t minValues = required == Required::Yes ? 1 : 0;
|
|
||||||
addArgument({ name, usageString, manString, minValues, values.max_size(), 0, getAcceptFunction(values) });
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(bool& value)
|
|
||||||
{
|
|
||||||
return [&value](const char*) -> bool {
|
|
||||||
value = true;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(const char*& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
value = input;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(std::string& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
value = input;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(std::string_view& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
value = input;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(int& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
const char* validate = input;
|
|
||||||
for (; *validate != '\0'; ++validate) {
|
|
||||||
// - [0-9]
|
|
||||||
if (*validate != 45 && (*validate < 48 || *validate > 57)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
value = std::stoi(input);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(unsigned int& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
const char* validate = input;
|
|
||||||
for (; *validate != '\0'; ++validate) {
|
|
||||||
// [0-9]
|
|
||||||
if (*validate < 48 || *validate > 57) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long convert = 0;
|
|
||||||
try {
|
|
||||||
convert = std::stoul(input);
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (convert <= std::numeric_limits<unsigned int>::max()) {
|
|
||||||
value = static_cast<unsigned int>(convert);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(double& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
const char* validate = input;
|
|
||||||
for (; *validate != '\0'; ++validate) {
|
|
||||||
// . [0-9]
|
|
||||||
if (*validate != 46 && (*validate < 48 || *validate > 57)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
value = std::stod(input);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
AcceptFunction ArgParser::getAcceptFunction(std::vector<std::string>& value)
|
|
||||||
{
|
|
||||||
return [&value](const char* input) -> bool {
|
|
||||||
value.push_back(input);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint8_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 : uint8_t {
|
|
||||||
No,
|
|
||||||
Yes,
|
|
||||||
Optional,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Error : uint8_t {
|
|
||||||
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
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdint> // int32_t
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream> // ifstream, ios, ofstream
|
|
||||||
#include <memory> // make_unique
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/meta/assert.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);
|
|
||||||
VERIFY(file.is_open(), "failed to open file: '{}'", path);
|
|
||||||
|
|
||||||
// Get length of the file
|
|
||||||
file.seekg(0, std::ios::end);
|
|
||||||
int32_t size = file.tellg();
|
|
||||||
file.seekg(0, std::ios::beg);
|
|
||||||
VERIFY(size != -1, "failed to read file length: '{}', path");
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
VERIFY(file.is_open(), "failed to open file: '{}'", m_path);
|
|
||||||
|
|
||||||
// Write data to disk
|
|
||||||
file.write(m_data.c_str(), m_data.size());
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021-2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# -*- yaml -*-
|
|
||||||
|
|
||||||
---
|
|
||||||
# FIXME: Figure out why NOLINTBEGIN/NOLINTEND doesnt work
|
|
||||||
Checks: -misc-unused-using-decls
|
|
||||||
...
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm> // min
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
|
|
||||||
#include <iomanip> // setprecision
|
|
||||||
#include <ios> // defaultfloat, fixed
|
|
||||||
#include <limits> // numeric_limits
|
|
||||||
#include <sstream> // stringstream
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
|
||||||
#include "util/meta/assert.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
void Builder::putLiteral(std::string_view literal)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < literal.length(); ++i) {
|
|
||||||
putCharacter(literal[i]);
|
|
||||||
if (literal[i] == '{' || literal[i] == '}') {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr std::string numberToString(size_t value, uint8_t base, bool uppercase)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
|
|
||||||
if (value > std::numeric_limits<uint32_t>::max()) {
|
|
||||||
result.reserve(64);
|
|
||||||
}
|
|
||||||
else if (value > std::numeric_limits<uint16_t>::max()) {
|
|
||||||
result.reserve(32);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr const auto& lookupLowercase = "0123456789abcdef";
|
|
||||||
constexpr const auto& lookupUppercase = "0123456789ABCDEF";
|
|
||||||
|
|
||||||
if (value == 0) {
|
|
||||||
result = '0';
|
|
||||||
}
|
|
||||||
else if (uppercase) {
|
|
||||||
while (value > 0) {
|
|
||||||
result.insert(0, 1, lookupUppercase[value % base]);
|
|
||||||
value /= base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
while (value > 0) {
|
|
||||||
result.insert(0, 1, lookupLowercase[value % base]);
|
|
||||||
value /= base;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Builder::putU64(size_t value,
|
|
||||||
uint8_t base,
|
|
||||||
bool uppercase,
|
|
||||||
char fill,
|
|
||||||
Align align,
|
|
||||||
Sign sign,
|
|
||||||
bool alternativeForm,
|
|
||||||
bool zeroPadding,
|
|
||||||
size_t width,
|
|
||||||
bool isNegative) const
|
|
||||||
{
|
|
||||||
std::string string = numberToString(value, base, uppercase);
|
|
||||||
|
|
||||||
// Sign
|
|
||||||
std::string signCharacter = "";
|
|
||||||
switch (sign) {
|
|
||||||
case Sign::None:
|
|
||||||
case Sign::Negative:
|
|
||||||
if (isNegative) {
|
|
||||||
signCharacter = '-';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Sign::Both:
|
|
||||||
signCharacter = (isNegative) ? '-' : '+';
|
|
||||||
break;
|
|
||||||
case Sign::Space:
|
|
||||||
signCharacter = (isNegative) ? '-' : ' ';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
};
|
|
||||||
if (align != Align::None || !zeroPadding) {
|
|
||||||
string.insert(0, signCharacter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alternative form
|
|
||||||
if (alternativeForm) {
|
|
||||||
switch (base) {
|
|
||||||
case 2:
|
|
||||||
string.insert(0, (uppercase) ? "0B" : "0b");
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
break;
|
|
||||||
string.insert(0, 1, '0');
|
|
||||||
case 10:
|
|
||||||
break;
|
|
||||||
case 16:
|
|
||||||
string.insert(0, (uppercase) ? "0X" : "0x");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zero padding
|
|
||||||
if (zeroPadding) {
|
|
||||||
fill = '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t length = string.length();
|
|
||||||
if (width < length) {
|
|
||||||
m_builder.write(string.data(), length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (align) {
|
|
||||||
case Align::Left:
|
|
||||||
m_builder.write(string.data(), length);
|
|
||||||
m_builder << std::string(width - length, fill);
|
|
||||||
break;
|
|
||||||
case Align::Center: {
|
|
||||||
size_t half = (width - length) / 2;
|
|
||||||
m_builder << std::string(half, fill);
|
|
||||||
m_builder.write(string.data(), length);
|
|
||||||
m_builder << std::string(width - half - length, fill);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Align::Right:
|
|
||||||
m_builder << std::string(width - length, fill);
|
|
||||||
m_builder.write(string.data(), length);
|
|
||||||
break;
|
|
||||||
case Align::None:
|
|
||||||
if (zeroPadding) {
|
|
||||||
m_builder << signCharacter;
|
|
||||||
if (signCharacter.empty()) {
|
|
||||||
m_builder << '0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_builder << std::string(width - length - zeroPadding, fill);
|
|
||||||
m_builder.write(string.data(), length);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void Builder::putI64(int64_t value,
|
|
||||||
uint8_t base,
|
|
||||||
bool uppercase,
|
|
||||||
char fill,
|
|
||||||
Align align,
|
|
||||||
Sign sign,
|
|
||||||
bool alternativeForm,
|
|
||||||
bool zeroPadding,
|
|
||||||
size_t width) const
|
|
||||||
{
|
|
||||||
bool isNegative = value < 0;
|
|
||||||
value = isNegative ? -value : value;
|
|
||||||
putU64(static_cast<uint64_t>(value), base, uppercase, fill, align, sign, alternativeForm, zeroPadding, width, isNegative);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Builder::putF64(double number, uint8_t precision) const
|
|
||||||
{
|
|
||||||
precision = std::min(precision, static_cast<uint8_t>(std::numeric_limits<double>::digits10));
|
|
||||||
|
|
||||||
std::stringstream stream;
|
|
||||||
stream
|
|
||||||
<< std::fixed << std::setprecision(precision)
|
|
||||||
<< number
|
|
||||||
<< std::defaultfloat << std::setprecision(6);
|
|
||||||
std::string string = stream.str();
|
|
||||||
m_builder << string;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Builder::putString(std::string_view string, char fill, Align align, size_t width) const
|
|
||||||
{
|
|
||||||
size_t length = string.length();
|
|
||||||
if (width < length) {
|
|
||||||
m_builder.write(string.data(), length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (align) {
|
|
||||||
case Align::None:
|
|
||||||
case Align::Left:
|
|
||||||
m_builder.write(string.data(), length);
|
|
||||||
m_builder << std::string(width - length, fill);
|
|
||||||
break;
|
|
||||||
case Align::Center: {
|
|
||||||
size_t half = (width - length) / 2;
|
|
||||||
m_builder << std::string(half, fill);
|
|
||||||
m_builder.write(string.data(), length);
|
|
||||||
m_builder << std::string(width - half - length, fill);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Align::Right:
|
|
||||||
m_builder << std::string(width - length, fill);
|
|
||||||
m_builder.write(string.data(), length);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // int32_t, int64_t, uint8_t, uint32_t
|
|
||||||
#include <sstream> // stringstream
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
class Builder {
|
|
||||||
public:
|
|
||||||
enum class Align : uint8_t {
|
|
||||||
None,
|
|
||||||
Left = 60, // '<'
|
|
||||||
Right = 62, // '>'
|
|
||||||
Center = 94, // '^'
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class Sign : uint8_t {
|
|
||||||
None,
|
|
||||||
Negative = 45, // '-'
|
|
||||||
Both = 43, // '+'
|
|
||||||
Space = 32, // ' '
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit Builder(std::stringstream& builder)
|
|
||||||
: m_builder(builder)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void putLiteral(std::string_view literal);
|
|
||||||
|
|
||||||
void putU64(size_t value,
|
|
||||||
uint8_t base = 10,
|
|
||||||
bool uppercase = false,
|
|
||||||
char fill = ' ',
|
|
||||||
Align align = Align::Right,
|
|
||||||
Sign sign = Sign::Negative,
|
|
||||||
bool alternativeForm = false,
|
|
||||||
bool zeroPadding = false,
|
|
||||||
size_t width = 0,
|
|
||||||
bool isNegative = false) const;
|
|
||||||
|
|
||||||
void putI64(int64_t value,
|
|
||||||
uint8_t base = 10,
|
|
||||||
bool uppercase = false,
|
|
||||||
char fill = ' ',
|
|
||||||
Align align = Align::Right,
|
|
||||||
Sign sign = Sign::Negative,
|
|
||||||
bool alternativeForm = false,
|
|
||||||
bool zeroPadding = false,
|
|
||||||
size_t width = 0) const;
|
|
||||||
|
|
||||||
void putF64(double number, uint8_t precision = 6) const;
|
|
||||||
void putCharacter(char character) const { m_builder.write(&character, 1); }
|
|
||||||
void putString(std::string_view string, char fill = ' ', Align align = Align::Left, size_t width = 0) const;
|
|
||||||
|
|
||||||
const std::stringstream& builder() const { return m_builder; }
|
|
||||||
std::stringstream& builder() { return m_builder; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::stringstream& m_builder;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdint> // uint8_t
|
|
||||||
#include <cstdio> // FILE, fputs, stdout
|
|
||||||
#include <sstream> // stringstream
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/color.h"
|
|
||||||
#include "util/format/format.h"
|
|
||||||
#include "util/meta/assert.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
TextStyle::TextStyle(Emphasis emphasis)
|
|
||||||
: m_emphasis(emphasis)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TextStyle::TextStyle(bool isForeground, TerminalColor color)
|
|
||||||
{
|
|
||||||
if (isForeground) {
|
|
||||||
m_foregroundColor = color;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_backgroundColor = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TextStyle& TextStyle::operator|=(const TextStyle& rhs)
|
|
||||||
{
|
|
||||||
if (m_foregroundColor == TerminalColor::None) {
|
|
||||||
m_foregroundColor = rhs.m_foregroundColor;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
VERIFY(rhs.m_foregroundColor == TerminalColor::None, "can't OR a terminal color");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_backgroundColor == TerminalColor::None) {
|
|
||||||
m_backgroundColor = rhs.m_backgroundColor;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
VERIFY(rhs.m_backgroundColor == TerminalColor::None, "can't OR a terminal color");
|
|
||||||
}
|
|
||||||
|
|
||||||
m_emphasis = static_cast<Emphasis>(static_cast<uint8_t>(m_emphasis) | static_cast<uint8_t>(rhs.m_emphasis));
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextStyle fg(TerminalColor foreground)
|
|
||||||
{
|
|
||||||
return TextStyle(true, foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextStyle bg(TerminalColor background)
|
|
||||||
{
|
|
||||||
return TextStyle(false, background);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextStyle operator|(TextStyle lhs, const TextStyle& rhs)
|
|
||||||
{
|
|
||||||
return lhs |= rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextStyle operator|(Emphasis lhs, Emphasis rhs)
|
|
||||||
{
|
|
||||||
return TextStyle { lhs } | rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator&(Emphasis lhs, Emphasis rhs)
|
|
||||||
{
|
|
||||||
return static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void setDisplayAttributes(std::stringstream& stream, const TextStyle& style)
|
|
||||||
{
|
|
||||||
bool hasForeground = style.foregroundColor() != TerminalColor::None;
|
|
||||||
bool hasBackground = style.backgroundColor() != TerminalColor::None;
|
|
||||||
bool hasEmphasis = style.emphasis() != Emphasis::None;
|
|
||||||
if (!hasForeground && !hasBackground && !hasEmphasis) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.write("\033[", 2);
|
|
||||||
|
|
||||||
if (hasForeground) {
|
|
||||||
stream << format("{}", static_cast<uint8_t>(style.foregroundColor()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasBackground) {
|
|
||||||
if (hasForeground) {
|
|
||||||
stream.write(";", 1);
|
|
||||||
}
|
|
||||||
stream << format("{}", static_cast<uint8_t>(style.backgroundColor()) + 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
stream.write("m", 1);
|
|
||||||
|
|
||||||
if (hasEmphasis) {
|
|
||||||
|
|
||||||
#define ESCAPE_ATTRIBUTE(escape, attribute) \
|
|
||||||
if (style.emphasis() & escape) { \
|
|
||||||
stream.write("\033[", 2); \
|
|
||||||
stream.write(attribute, 1); \
|
|
||||||
stream.write("m", 1); \
|
|
||||||
}
|
|
||||||
|
|
||||||
ESCAPE_ATTRIBUTE(Emphasis::Bold, "1");
|
|
||||||
ESCAPE_ATTRIBUTE(Emphasis::Faint, "2");
|
|
||||||
ESCAPE_ATTRIBUTE(Emphasis::Italic, "3");
|
|
||||||
ESCAPE_ATTRIBUTE(Emphasis::Underline, "4");
|
|
||||||
ESCAPE_ATTRIBUTE(Emphasis::Blink, "5");
|
|
||||||
ESCAPE_ATTRIBUTE(Emphasis::Reverse, "7");
|
|
||||||
ESCAPE_ATTRIBUTE(Emphasis::Conceal, "8");
|
|
||||||
ESCAPE_ATTRIBUTE(Emphasis::Strikethrough, "9");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void coloredVariadicFormat(std::stringstream& stream, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters)
|
|
||||||
{
|
|
||||||
setDisplayAttributes(stream, style);
|
|
||||||
variadicFormat(stream, format, parameters);
|
|
||||||
stream.write("\033[0m", 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void coloredVariadicPrint(FILE* file, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters)
|
|
||||||
{
|
|
||||||
std::stringstream stream;
|
|
||||||
setDisplayAttributes(stream, style);
|
|
||||||
variadicFormat(stream, format, parameters);
|
|
||||||
stream.write("\033[0m", 4);
|
|
||||||
|
|
||||||
std::string string = stream.str();
|
|
||||||
fputs(string.c_str(), file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
ColorPrintOperatorStyle::ColorPrintOperatorStyle(FILE* file, const TextStyle& style)
|
|
||||||
: m_file(file)
|
|
||||||
, m_style(style)
|
|
||||||
, m_stream()
|
|
||||||
, m_builder(m_stream)
|
|
||||||
{
|
|
||||||
setDisplayAttributes(m_stream, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorPrintOperatorStyle::~ColorPrintOperatorStyle()
|
|
||||||
{
|
|
||||||
m_stream.write("\033[0m", 4);
|
|
||||||
std::string string = m_stream.str();
|
|
||||||
fputs(string.c_str(), m_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorPrintOperatorStyle print(const TextStyle& style)
|
|
||||||
{
|
|
||||||
return ColorPrintOperatorStyle(stdout, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorPrintOperatorStyle print(FILE* file, const TextStyle& style)
|
|
||||||
{
|
|
||||||
return ColorPrintOperatorStyle(file, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint8_t
|
|
||||||
#include <cstdio> // FILE, stdout
|
|
||||||
#include <sstream> // stringstream
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/format.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
enum class TerminalColor : uint8_t {
|
|
||||||
None = 0,
|
|
||||||
Black = 30,
|
|
||||||
Red,
|
|
||||||
Green,
|
|
||||||
Yellow,
|
|
||||||
Blue,
|
|
||||||
Magenta,
|
|
||||||
Cyan,
|
|
||||||
White,
|
|
||||||
BrightBlack = 90,
|
|
||||||
BrightRed,
|
|
||||||
BrightGreen,
|
|
||||||
BrightYellow,
|
|
||||||
BrightBlue,
|
|
||||||
BrightMagenta,
|
|
||||||
BrightCyan,
|
|
||||||
BrightWhite
|
|
||||||
};
|
|
||||||
|
|
||||||
// Bit field
|
|
||||||
enum class Emphasis : uint8_t {
|
|
||||||
None = 0, // Attribute 0
|
|
||||||
Bold = 1, // Attribute 1
|
|
||||||
Faint = 1 << 1, // Attribute 2
|
|
||||||
Italic = 1 << 2, // Attribute 3
|
|
||||||
Underline = 1 << 3, // Attribute 4
|
|
||||||
Blink = 1 << 4, // Attribute 5
|
|
||||||
Reverse = 1 << 5, // Attribute 7
|
|
||||||
Conceal = 1 << 6, // Attribute 8
|
|
||||||
Strikethrough = 1 << 7, // Attribute 9
|
|
||||||
};
|
|
||||||
|
|
||||||
class TextStyle {
|
|
||||||
private:
|
|
||||||
friend TextStyle fg(TerminalColor foreground);
|
|
||||||
friend TextStyle bg(TerminalColor background);
|
|
||||||
|
|
||||||
public:
|
|
||||||
TextStyle(Emphasis emphasis);
|
|
||||||
|
|
||||||
// Operator pipe equal, reads the same way as +=
|
|
||||||
TextStyle& operator|=(const TextStyle& rhs);
|
|
||||||
|
|
||||||
TerminalColor foregroundColor() const { return m_foregroundColor; }
|
|
||||||
TerminalColor backgroundColor() const { return m_backgroundColor; }
|
|
||||||
Emphasis emphasis() const { return m_emphasis; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
TextStyle(bool isForeground, TerminalColor color);
|
|
||||||
|
|
||||||
TerminalColor m_foregroundColor { TerminalColor::None };
|
|
||||||
TerminalColor m_backgroundColor { TerminalColor::None };
|
|
||||||
Emphasis m_emphasis { 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
TextStyle fg(TerminalColor foreground);
|
|
||||||
TextStyle bg(TerminalColor background);
|
|
||||||
TextStyle operator|(TextStyle lhs, const TextStyle& rhs);
|
|
||||||
TextStyle operator|(Emphasis lhs, Emphasis rhs);
|
|
||||||
bool operator&(Emphasis lhs, Emphasis rhs);
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void coloredVariadicFormat(std::stringstream& stream, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters);
|
|
||||||
|
|
||||||
template<typename... Parameters>
|
|
||||||
std::string format(const TextStyle& style, std::string_view format, const Parameters&... parameters)
|
|
||||||
{
|
|
||||||
std::stringstream stream;
|
|
||||||
VariadicParameters variadicParameters { parameters... };
|
|
||||||
coloredVariadicFormat(stream, style, format, variadicParameters);
|
|
||||||
return stream.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Parameters>
|
|
||||||
void formatTo(std::string& output, const TextStyle& style, std::string_view format, const Parameters&... parameters)
|
|
||||||
{
|
|
||||||
std::stringstream stream;
|
|
||||||
VariadicParameters variadicParameters { parameters... };
|
|
||||||
coloredVariadicFormat(stream, style, format, variadicParameters);
|
|
||||||
output += stream.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void coloredVariadicPrint(FILE* file, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters);
|
|
||||||
|
|
||||||
template<size_t N, typename... Parameters>
|
|
||||||
void print(const TextStyle& style, const char (&format)[N], const Parameters&... parameters)
|
|
||||||
{
|
|
||||||
VariadicParameters variadicParameters { parameters... };
|
|
||||||
coloredVariadicPrint(stdout, style, { format, N - 1 }, variadicParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<size_t N, typename... Parameters>
|
|
||||||
void print(FILE* file, const TextStyle& style, const char (&format)[N], const Parameters&... parameters)
|
|
||||||
{
|
|
||||||
VariadicParameters variadicParameters { parameters... };
|
|
||||||
coloredVariadicPrint(file, style, { format, N - 1 }, variadicParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
class ColorPrintOperatorStyle {
|
|
||||||
public:
|
|
||||||
ColorPrintOperatorStyle(FILE* file, const TextStyle& style);
|
|
||||||
virtual ~ColorPrintOperatorStyle();
|
|
||||||
|
|
||||||
Builder& builder() { return m_builder; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
FILE* m_file;
|
|
||||||
TextStyle m_style;
|
|
||||||
|
|
||||||
std::stringstream m_stream;
|
|
||||||
Builder m_builder;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
const ColorPrintOperatorStyle& operator<<(const ColorPrintOperatorStyle& colorPrintOperatorStyle, const T& value)
|
|
||||||
{
|
|
||||||
_format(const_cast<ColorPrintOperatorStyle&>(colorPrintOperatorStyle).builder(), value);
|
|
||||||
return colorPrintOperatorStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorPrintOperatorStyle print(const TextStyle& style);
|
|
||||||
ColorPrintOperatorStyle print(FILE* file, const TextStyle& style);
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
using Util::Format::format;
|
|
||||||
using Util::Format::formatTo;
|
|
||||||
using Util::Format::print;
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <sstream> // stringstream
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
|
||||||
#include "util/format/format.h"
|
|
||||||
#include "util/format/parser.h"
|
|
||||||
#include "util/meta/assert.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters)
|
|
||||||
{
|
|
||||||
// Consume everything until '{' or EOF
|
|
||||||
const auto literal = parser.consumeLiteral();
|
|
||||||
builder.putLiteral(literal);
|
|
||||||
|
|
||||||
// Reached end of format string
|
|
||||||
if (parser.isEOF()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consume index + ':'
|
|
||||||
auto indexMaybe = parser.consumeIndex();
|
|
||||||
|
|
||||||
// Get parameter at index, or next
|
|
||||||
size_t index = indexMaybe.has_value() ? indexMaybe.value() : parameters.tell();
|
|
||||||
VERIFY(index < parameters.size(), "argument not found at index '%zu'", index);
|
|
||||||
auto& parameter = parameters.parameter(index);
|
|
||||||
|
|
||||||
// Format the parameter
|
|
||||||
parameter.format(builder, parser, parameter.value);
|
|
||||||
|
|
||||||
// Go to next parameter
|
|
||||||
parameters.ignore();
|
|
||||||
|
|
||||||
// Recurse
|
|
||||||
if (!parser.isEOF()) {
|
|
||||||
variadicFormatImpl(builder, parser, parameters);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void variadicFormat(std::stringstream& stream, std::string_view format, TypeErasedParameters& parameters)
|
|
||||||
{
|
|
||||||
Builder builder { stream };
|
|
||||||
Parser parser { format, parameters.size() };
|
|
||||||
|
|
||||||
variadicFormatImpl(builder, parser, parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
FormatOperatorStyle::FormatOperatorStyle(std::string& output)
|
|
||||||
: m_output(output)
|
|
||||||
, m_stream()
|
|
||||||
, m_builder(m_stream)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
FormatOperatorStyle::~FormatOperatorStyle()
|
|
||||||
{
|
|
||||||
m_output = m_stream.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
FormatOperatorStyle formatTo(std::string& output)
|
|
||||||
{
|
|
||||||
return FormatOperatorStyle(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <span>
|
|
||||||
#include <sstream> // stringstream
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/formatter.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
class Builder;
|
|
||||||
class Parser;
|
|
||||||
|
|
||||||
struct Parameter {
|
|
||||||
const void* value;
|
|
||||||
void (*format)(Builder& builder, Parser& parser, const void* value);
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
void formatParameterValue(Builder& builder, Parser& parser, const void* value)
|
|
||||||
{
|
|
||||||
Formatter<T> formatter;
|
|
||||||
formatter.parse(parser);
|
|
||||||
formatter.format(builder, *static_cast<const T*>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type erasure improves both compile time and binary size significantly
|
|
||||||
class TypeErasedParameters {
|
|
||||||
public:
|
|
||||||
const Parameter parameter(size_t index) { return m_parameters[index]; }
|
|
||||||
|
|
||||||
size_t tell() const { return m_index; }
|
|
||||||
size_t size() const { return m_parameters.size(); }
|
|
||||||
bool isEOF() const { return m_index >= m_parameters.size(); }
|
|
||||||
void ignore() { m_index++; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
size_t m_index { 0 };
|
|
||||||
std::span<const Parameter> m_parameters;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename... Parameters>
|
|
||||||
class VariadicParameters final : public TypeErasedParameters {
|
|
||||||
public:
|
|
||||||
VariadicParameters(const Parameters&... parameters)
|
|
||||||
: m_templatedParameters({ { ¶meters, formatParameterValue<Parameters> }... })
|
|
||||||
{
|
|
||||||
m_parameters = m_templatedParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::array<Parameter, sizeof...(Parameters)> m_templatedParameters;
|
|
||||||
};
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void variadicFormatImpl(Builder& builder, Parser& parser, TypeErasedParameters& parameters);
|
|
||||||
void variadicFormat(std::stringstream& stream, std::string_view format, TypeErasedParameters& parameters);
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
template<typename... Parameters>
|
|
||||||
std::string format(std::string_view format, const Parameters&... parameters)
|
|
||||||
{
|
|
||||||
std::stringstream stream;
|
|
||||||
VariadicParameters variadicParameters { parameters... };
|
|
||||||
variadicFormat(stream, format, variadicParameters);
|
|
||||||
return stream.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... Parameters>
|
|
||||||
void formatTo(std::string& output, std::string_view format, const Parameters&... parameters)
|
|
||||||
{
|
|
||||||
std::stringstream stream;
|
|
||||||
VariadicParameters variadicParameters { parameters... };
|
|
||||||
variadicFormat(stream, format, variadicParameters);
|
|
||||||
output += stream.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
class FormatOperatorStyle {
|
|
||||||
public:
|
|
||||||
FormatOperatorStyle(std::string& output);
|
|
||||||
virtual ~FormatOperatorStyle();
|
|
||||||
|
|
||||||
Builder& builder() { return m_builder; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::string& m_output;
|
|
||||||
std::stringstream m_stream;
|
|
||||||
Builder m_builder;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
const FormatOperatorStyle& operator<<(const FormatOperatorStyle& formatOperatorStyle, const T& value)
|
|
||||||
{
|
|
||||||
_format(const_cast<FormatOperatorStyle&>(formatOperatorStyle).builder(), value);
|
|
||||||
return formatOperatorStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
FormatOperatorStyle formatTo(std::string& output);
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
using Util::Format::format;
|
|
||||||
using Util::Format::formatTo;
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // int32_t, uint32_t, int64_t,
|
|
||||||
#include <cstring> // strlen
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
|
||||||
#include "util/format/formatter.h"
|
|
||||||
#include "util/format/parser.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
// Char
|
|
||||||
|
|
||||||
template<>
|
|
||||||
void Formatter<char>::format(Builder& builder, char value) const
|
|
||||||
{
|
|
||||||
if (specifier.type != PresentationType::None && specifier.type != PresentationType::Character) {
|
|
||||||
// "Type char is a distinct type that has an implementation-defined
|
|
||||||
// choice of “signed char” or “unsigned char” as its underlying type."
|
|
||||||
// http://eel.is/c++draft/basic#fundamental
|
|
||||||
Formatter<signed char> formatter { .specifier = specifier };
|
|
||||||
return formatter.format(builder, static_cast<signed char>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
Formatter<std::string_view> formatter { .specifier = specifier };
|
|
||||||
return formatter.format(builder, { &value, 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
template<>
|
|
||||||
void Formatter<bool>::format(Builder& builder, bool value) const
|
|
||||||
{
|
|
||||||
switch (specifier.type) {
|
|
||||||
case PresentationType::Binary:
|
|
||||||
case PresentationType::BinaryUppercase:
|
|
||||||
case PresentationType::Character:
|
|
||||||
case PresentationType::Decimal:
|
|
||||||
case PresentationType::Octal:
|
|
||||||
case PresentationType::Hex:
|
|
||||||
case PresentationType::HexUppercase: {
|
|
||||||
Formatter<uint8_t> formatter { .specifier = specifier };
|
|
||||||
return formatter.format(builder, static_cast<uint8_t>(value));
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
Formatter<std::string_view> formatter { .specifier = specifier };
|
|
||||||
formatter.format(builder, value ? "true" : "false");
|
|
||||||
}
|
|
||||||
|
|
||||||
// String
|
|
||||||
|
|
||||||
template<>
|
|
||||||
void Formatter<std::string_view>::format(Builder& builder, std::string_view value) const
|
|
||||||
{
|
|
||||||
builder.putString(value, specifier.fill, specifier.align, specifier.width);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Formatter<const char*>::parse(Parser& parser)
|
|
||||||
{
|
|
||||||
parser.parseSpecifier(specifier, Parser::ParameterType::CString);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Formatter<const char*>::format(Builder& builder, const char* value) const
|
|
||||||
{
|
|
||||||
if (specifier.type == PresentationType::Pointer) {
|
|
||||||
Formatter<uintptr_t> formatter { .specifier = specifier };
|
|
||||||
formatter.specifier.alternativeForm = true;
|
|
||||||
formatter.specifier.type = PresentationType::Hex;
|
|
||||||
return formatter.format(builder, reinterpret_cast<uintptr_t>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
Formatter<std::string_view>::format(
|
|
||||||
builder,
|
|
||||||
value != nullptr ? std::string_view { value, strlen(value) } : "nullptr");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pointer
|
|
||||||
|
|
||||||
void Formatter<std::nullptr_t>::format(Builder& builder, std::nullptr_t) const
|
|
||||||
{
|
|
||||||
Formatter<const void*>::format(builder, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,267 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // int8_t, int32_t, int64_t, uint8_t, uint32_t, uintptr_t
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <type_traits> // is_integral_v, is_same
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
|
||||||
#include "util/format/parser.h"
|
|
||||||
#include "util/meta/concepts.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
enum class PresentationType : uint8_t {
|
|
||||||
None, // Defaults are any of: 'dgcsp', depending on the type
|
|
||||||
// Interger
|
|
||||||
Binary = 98, // 'b'
|
|
||||||
BinaryUppercase = 66, // 'B'
|
|
||||||
Decimal = 100, // 'd'
|
|
||||||
Octal = 111, // 'o'
|
|
||||||
Hex = 120, // 'x'
|
|
||||||
HexUppercase = 88, // 'X'
|
|
||||||
// Floating-point
|
|
||||||
Hexfloat = 97, // 'a'
|
|
||||||
HexfloatUppercase = 65, // 'A'
|
|
||||||
Exponent = 101, // 'e'
|
|
||||||
ExponentUppercase = 69, // 'E'
|
|
||||||
FixedPoint = 102, // 'f'
|
|
||||||
FixedPointUppercase = 70, // 'F'
|
|
||||||
General = 103, // 'g'
|
|
||||||
GeneralUppercase = 71, // 'G'
|
|
||||||
// Character
|
|
||||||
Character = 99, // 'c'
|
|
||||||
// String
|
|
||||||
String = 115, // 's'
|
|
||||||
// Pointer
|
|
||||||
Pointer = 112, // 'p'
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Specifier {
|
|
||||||
char fill = ' ';
|
|
||||||
Builder::Align align = Builder::Align::None;
|
|
||||||
|
|
||||||
Builder::Sign sign = Builder::Sign::None;
|
|
||||||
|
|
||||||
bool alternativeForm = false;
|
|
||||||
bool zeroPadding = false;
|
|
||||||
size_t width = 0;
|
|
||||||
int8_t precision = -1;
|
|
||||||
|
|
||||||
PresentationType type = PresentationType::None;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct Formatter {
|
|
||||||
Specifier specifier;
|
|
||||||
|
|
||||||
constexpr void parse(Parser& parser)
|
|
||||||
{
|
|
||||||
if constexpr (std::is_same_v<T, char>) {
|
|
||||||
parser.parseSpecifier(specifier, Parser::ParameterType::Char);
|
|
||||||
}
|
|
||||||
else if (std::is_same_v<T, bool>) {
|
|
||||||
parser.parseSpecifier(specifier, Parser::ParameterType::Char);
|
|
||||||
}
|
|
||||||
else if (std::is_same_v<T, std::string_view>) {
|
|
||||||
parser.parseSpecifier(specifier, Parser::ParameterType::String);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void format(Builder&, T) const {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Integral
|
|
||||||
|
|
||||||
template<Integral T>
|
|
||||||
struct Formatter<T> {
|
|
||||||
Specifier specifier;
|
|
||||||
|
|
||||||
void parse(Parser& parser)
|
|
||||||
{
|
|
||||||
parser.parseSpecifier(specifier, Parser::ParameterType::Integral);
|
|
||||||
}
|
|
||||||
|
|
||||||
void format(Builder& builder, T value) const
|
|
||||||
{
|
|
||||||
if (specifier.type == PresentationType::Character) {
|
|
||||||
assert(value >= 0 && value <= 127);
|
|
||||||
|
|
||||||
Formatter<std::string_view> formatter { .specifier = specifier };
|
|
||||||
formatter.specifier.type = PresentationType::String;
|
|
||||||
return formatter.format(builder, { reinterpret_cast<const char*>(&value), 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t base = 0;
|
|
||||||
bool uppercase = false;
|
|
||||||
switch (specifier.type) {
|
|
||||||
case PresentationType::BinaryUppercase:
|
|
||||||
uppercase = true;
|
|
||||||
case PresentationType::Binary:
|
|
||||||
base = 2;
|
|
||||||
break;
|
|
||||||
case PresentationType::Octal:
|
|
||||||
base = 8;
|
|
||||||
break;
|
|
||||||
case PresentationType::None:
|
|
||||||
case PresentationType::Decimal:
|
|
||||||
base = 10;
|
|
||||||
break;
|
|
||||||
case PresentationType::HexUppercase:
|
|
||||||
uppercase = true;
|
|
||||||
case PresentationType::Hex:
|
|
||||||
base = 16;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
if constexpr (std::is_unsigned_v<T>) {
|
|
||||||
builder.putU64(
|
|
||||||
value, base, uppercase, specifier.fill, specifier.align, specifier.sign,
|
|
||||||
specifier.alternativeForm, specifier.zeroPadding, specifier.width);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
builder.putI64(
|
|
||||||
value, base, uppercase, specifier.fill, specifier.align, specifier.sign,
|
|
||||||
specifier.alternativeForm, specifier.zeroPadding, specifier.width);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Floating point
|
|
||||||
|
|
||||||
template<FloatingPoint T>
|
|
||||||
struct Formatter<T> {
|
|
||||||
Specifier specifier;
|
|
||||||
|
|
||||||
void parse(Parser& parser)
|
|
||||||
{
|
|
||||||
parser.parseSpecifier(specifier, Parser::ParameterType::FloatingPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
void format(Builder& builder, T value) const
|
|
||||||
{
|
|
||||||
if (specifier.precision < 0) {
|
|
||||||
builder.putF64(value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.putF64(value, specifier.precision);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Char
|
|
||||||
|
|
||||||
template<>
|
|
||||||
void Formatter<char>::format(Builder& builder, char value) const;
|
|
||||||
|
|
||||||
template<>
|
|
||||||
void Formatter<bool>::format(Builder& builder, bool value) const;
|
|
||||||
|
|
||||||
// String
|
|
||||||
|
|
||||||
template<>
|
|
||||||
void Formatter<std::string_view>::format(Builder& builder, std::string_view value) const;
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct Formatter<std::string> : Formatter<std::string_view> {
|
|
||||||
};
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct Formatter<const char*> : Formatter<std::string_view> {
|
|
||||||
void parse(Parser& parser);
|
|
||||||
void format(Builder& builder, const char* value) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct Formatter<char*> : Formatter<const char*> {
|
|
||||||
};
|
|
||||||
|
|
||||||
template<size_t N>
|
|
||||||
struct Formatter<char[N]> : Formatter<const char*> {
|
|
||||||
};
|
|
||||||
|
|
||||||
// Pointer
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct Formatter<T*> : Formatter<uintptr_t> {
|
|
||||||
constexpr void parse(Parser& parser)
|
|
||||||
{
|
|
||||||
parser.parseSpecifier(specifier, Parser::ParameterType::Pointer);
|
|
||||||
specifier.alternativeForm = true;
|
|
||||||
specifier.type = PresentationType::Hex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void format(Builder& builder, T* value) const
|
|
||||||
{
|
|
||||||
Formatter<uintptr_t>::format(builder, reinterpret_cast<uintptr_t>(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct Formatter<std::nullptr_t> : Formatter<const void*> {
|
|
||||||
void format(Builder& builder, std::nullptr_t) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Container
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct Formatter<std::vector<T>> : Formatter<T> {
|
|
||||||
void format(Builder& builder, const std::vector<T>& value) const
|
|
||||||
{
|
|
||||||
builder.putString("{\n");
|
|
||||||
for (auto it = value.cbegin(); it != value.cend(); ++it) {
|
|
||||||
builder.putString(" ");
|
|
||||||
Formatter<T>::format(builder, *it);
|
|
||||||
|
|
||||||
// Add comma, except after the last element
|
|
||||||
if (it != std::prev(value.end(), 1)) {
|
|
||||||
builder.putCharacter(',');
|
|
||||||
}
|
|
||||||
builder.putCharacter('\n');
|
|
||||||
}
|
|
||||||
builder.putCharacter('}');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#define UTIL_FORMAT_FORMAT_AS_MAP(type) \
|
|
||||||
template<typename K, typename V> \
|
|
||||||
struct Formatter<type<K, V>> \
|
|
||||||
: Formatter<K> \
|
|
||||||
, Formatter<V> { \
|
|
||||||
void format(Builder& builder, const type<K, V>& value) const \
|
|
||||||
{ \
|
|
||||||
builder.putString("{\n"); \
|
|
||||||
auto last = value.end(); \
|
|
||||||
for (auto it = value.begin(); it != last; ++it) { \
|
|
||||||
builder.putString(R"( ")"); \
|
|
||||||
Formatter<K>::format(builder, it->first); \
|
|
||||||
builder.putString(R"(": )"); \
|
|
||||||
Formatter<V>::format(builder, it->second); \
|
|
||||||
\
|
|
||||||
/* Add comma, except after the last element */ \
|
|
||||||
if (std::next(it) != last) { \
|
|
||||||
builder.putCharacter(','); \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
builder.putCharacter('\n'); \
|
|
||||||
} \
|
|
||||||
builder.putCharacter('}'); \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
UTIL_FORMAT_FORMAT_AS_MAP(std::map);
|
|
||||||
UTIL_FORMAT_FORMAT_AS_MAP(std::unordered_map);
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdio> // FILE
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "util/format/color.h"
|
|
||||||
#include "util/format/log.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
std::string formatTimeElapsed()
|
|
||||||
{
|
|
||||||
return format("{:.3}s ", s_timer.elapsedNanoseconds() / 1000000000.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string formatType(LogType type)
|
|
||||||
{
|
|
||||||
std::string output;
|
|
||||||
|
|
||||||
formatTo(output, "[");
|
|
||||||
switch (type) {
|
|
||||||
case LogType::Trace:
|
|
||||||
formatTo(output, "trace");
|
|
||||||
break;
|
|
||||||
case LogType::Debug:
|
|
||||||
formatTo(output, fg(TerminalColor::Magenta), "debug");
|
|
||||||
break;
|
|
||||||
case LogType::Success:
|
|
||||||
formatTo(output, fg(TerminalColor::Green), "success");
|
|
||||||
break;
|
|
||||||
case LogType::Info:
|
|
||||||
formatTo(output, fg(TerminalColor::Blue), "info");
|
|
||||||
break;
|
|
||||||
case LogType::Warn:
|
|
||||||
formatTo(output, Emphasis::Bold | fg(TerminalColor::Yellow), "warn");
|
|
||||||
break;
|
|
||||||
case LogType::Error:
|
|
||||||
formatTo(output, Emphasis::Bold | fg(TerminalColor::Red), "error");
|
|
||||||
break;
|
|
||||||
case LogType::Critical:
|
|
||||||
formatTo(output, Emphasis::Bold | fg(TerminalColor::White) | bg(TerminalColor::Red), "critical");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
formatTo(output, "] ");
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
LogOperatorStyle::LogOperatorStyle(FILE* file, LogType type)
|
|
||||||
: m_file(file)
|
|
||||||
, m_type(type)
|
|
||||||
, m_stream()
|
|
||||||
, m_builder(m_stream)
|
|
||||||
{
|
|
||||||
m_stream << formatTimeElapsed();
|
|
||||||
m_stream << formatType(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogOperatorStyle::~LogOperatorStyle()
|
|
||||||
{
|
|
||||||
m_stream.write("\n", 1);
|
|
||||||
std::string string = m_stream.str();
|
|
||||||
fputs(string.c_str(), m_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogOperatorStyle trace()
|
|
||||||
{
|
|
||||||
return LogOperatorStyle(stdout, LogType::Trace);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogOperatorStyle debug()
|
|
||||||
{
|
|
||||||
return LogOperatorStyle(stdout, LogType::Debug);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogOperatorStyle success()
|
|
||||||
{
|
|
||||||
return LogOperatorStyle(stdout, LogType::Success);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogOperatorStyle info()
|
|
||||||
{
|
|
||||||
return LogOperatorStyle(stdout, LogType::Info);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogOperatorStyle warn()
|
|
||||||
{
|
|
||||||
return LogOperatorStyle(stderr, LogType::Warn);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogOperatorStyle error()
|
|
||||||
{
|
|
||||||
return LogOperatorStyle(stderr, LogType::Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
LogOperatorStyle critical()
|
|
||||||
{
|
|
||||||
return LogOperatorStyle(stderr, LogType::Critical);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdio> // FILE, stderr, stdout
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/format.h"
|
|
||||||
#include "util/format/print.h"
|
|
||||||
#include "util/timer.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
static Util::Timer s_timer;
|
|
||||||
|
|
||||||
enum class LogType : uint8_t {
|
|
||||||
Trace, // White
|
|
||||||
Debug, // Purple
|
|
||||||
Success, // Green
|
|
||||||
Info, // Blue
|
|
||||||
Warn, // Bold yellow
|
|
||||||
Error, // Bold red
|
|
||||||
Critical, // Bold on red
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string formatTimeElapsed();
|
|
||||||
std::string formatType(LogType type);
|
|
||||||
|
|
||||||
#define LOG_FUNCTION(name, file, type) \
|
|
||||||
template<size_t N, typename... Parameters> \
|
|
||||||
void name(const char(&format)[N], const Parameters&... parameters) \
|
|
||||||
{ \
|
|
||||||
print(file, "{}", formatTimeElapsed()); \
|
|
||||||
print(file, "{}", formatType(type)); \
|
|
||||||
VariadicParameters variadicParameters { parameters... }; \
|
|
||||||
print(file, format, variadicParameters); \
|
|
||||||
print(file, "\n"); \
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_FUNCTION(trace, stdout, LogType::Trace);
|
|
||||||
LOG_FUNCTION(debug, stdout, LogType::Debug);
|
|
||||||
LOG_FUNCTION(success, stdout, LogType::Success);
|
|
||||||
LOG_FUNCTION(info, stdout, LogType::Info);
|
|
||||||
LOG_FUNCTION(warn, stderr, LogType::Warn);
|
|
||||||
LOG_FUNCTION(error, stderr, LogType::Error);
|
|
||||||
LOG_FUNCTION(critical, stderr, LogType::Critical);
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
class LogOperatorStyle {
|
|
||||||
public:
|
|
||||||
LogOperatorStyle(FILE* file, LogType type);
|
|
||||||
virtual ~LogOperatorStyle();
|
|
||||||
|
|
||||||
Builder& builder() { return m_builder; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
FILE* m_file;
|
|
||||||
LogType m_type;
|
|
||||||
|
|
||||||
std::stringstream m_stream;
|
|
||||||
Builder m_builder;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
const LogOperatorStyle& operator<<(const LogOperatorStyle& logOperatorStyle, const T& value)
|
|
||||||
{
|
|
||||||
_format(const_cast<LogOperatorStyle&>(logOperatorStyle).builder(), value);
|
|
||||||
return logOperatorStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogOperatorStyle trace();
|
|
||||||
LogOperatorStyle debug();
|
|
||||||
LogOperatorStyle success();
|
|
||||||
LogOperatorStyle info();
|
|
||||||
LogOperatorStyle warn();
|
|
||||||
LogOperatorStyle error();
|
|
||||||
LogOperatorStyle critical();
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
using Util::Format::critical;
|
|
||||||
using Util::Format::debug;
|
|
||||||
using Util::Format::error;
|
|
||||||
using Util::Format::info;
|
|
||||||
using Util::Format::success;
|
|
||||||
using Util::Format::trace;
|
|
||||||
using Util::Format::warn;
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,390 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm> // replace
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // int8_t
|
|
||||||
#include <limits> // numeric_limits
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
|
||||||
#include "util/format/formatter.h"
|
|
||||||
#include "util/format/parser.h"
|
|
||||||
#include "util/meta/assert.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
Parser::Parser(std::string_view format, size_t parameterCount)
|
|
||||||
: GenericLexer(format)
|
|
||||||
, m_parameterCount(parameterCount)
|
|
||||||
{
|
|
||||||
checkFormatParameterConsistency();
|
|
||||||
}
|
|
||||||
|
|
||||||
Parser::~Parser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void Parser::checkFormatParameterConsistency()
|
|
||||||
{
|
|
||||||
size_t braceOpen = 0;
|
|
||||||
size_t braceClose = 0;
|
|
||||||
while (!isEOF()) {
|
|
||||||
char peek0 = peek();
|
|
||||||
char peek1 = peek(1);
|
|
||||||
|
|
||||||
if (peek0 == '{' && peek1 == '{') {
|
|
||||||
ignore(2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peek0 == '}' && peek1 == '}') {
|
|
||||||
ignore(2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peek0 == '{') {
|
|
||||||
braceOpen++;
|
|
||||||
|
|
||||||
if (peek1 >= '0' && peek1 <= '9') {
|
|
||||||
m_mode = ArgumentIndexingMode::Manual;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (peek0 == '}') {
|
|
||||||
braceClose++;
|
|
||||||
}
|
|
||||||
|
|
||||||
ignore();
|
|
||||||
}
|
|
||||||
m_index = 0;
|
|
||||||
|
|
||||||
VERIFY(!(braceOpen < braceClose), "extra open braces in format string");
|
|
||||||
VERIFY(!(braceOpen > braceClose), "extra closing braces in format string");
|
|
||||||
|
|
||||||
if (m_mode == ArgumentIndexingMode::Automatic) {
|
|
||||||
VERIFY(!(braceOpen < m_parameterCount), "format string does not reference all passed parameters");
|
|
||||||
VERIFY(!(braceOpen > m_parameterCount), "format string references nonexistent parameter");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Parser::stringToNumber(std::string_view value)
|
|
||||||
{
|
|
||||||
size_t result = 0;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < value.length(); ++i) {
|
|
||||||
VERIFY(value[i] >= '0' && value[i] <= '9', "unexpected '%c'", value[i]);
|
|
||||||
result *= 10;
|
|
||||||
result += value[i] - '0'; // Subtract ASCII 48 to get the number
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string_view Parser::consumeLiteral()
|
|
||||||
{
|
|
||||||
const auto begin = tell();
|
|
||||||
|
|
||||||
while (!isEOF()) {
|
|
||||||
char peek0 = peek();
|
|
||||||
char peek1 = peek(1);
|
|
||||||
|
|
||||||
if (peek0 == '{' && peek1 == '{') {
|
|
||||||
ignore(2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peek0 == '}' && peek1 == '}') {
|
|
||||||
ignore(2);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get literal before the specifier {}
|
|
||||||
if (peek0 == '{' || peek0 == '}') {
|
|
||||||
return m_input.substr(begin, tell() - begin);
|
|
||||||
}
|
|
||||||
|
|
||||||
ignore();
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_input.substr(begin);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<size_t> Parser::consumeIndex()
|
|
||||||
{
|
|
||||||
if (!consumeSpecific('{')) {
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (m_mode) {
|
|
||||||
case ArgumentIndexingMode::Automatic: {
|
|
||||||
VERIFY(consumeSpecific(':') || peek() == '}', "expecting ':' or '}', not '%c'", peek());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
case ArgumentIndexingMode::Manual: {
|
|
||||||
const auto begin = tell();
|
|
||||||
|
|
||||||
while (!isEOF()) {
|
|
||||||
char peek0 = peek();
|
|
||||||
if (peek0 == '}' || peek0 == ':') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY(peek0 >= '0' && peek0 <= '9', "expecting number, not '%c'", peek0);
|
|
||||||
|
|
||||||
ignore();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t result = stringToNumber(m_input.substr(begin, tell() - begin));
|
|
||||||
|
|
||||||
if (peek() == ':') {
|
|
||||||
ignore();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Parser::parseSpecifier(Specifier& specifier, ParameterType type)
|
|
||||||
{
|
|
||||||
if (consumeSpecific('}') || isEOF()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alignment
|
|
||||||
char peek0 = peek();
|
|
||||||
char peek1 = peek(1);
|
|
||||||
if (peek1 == '<' || peek1 == '>' || peek1 == '^') {
|
|
||||||
specifier.fill = peek0;
|
|
||||||
specifier.align = static_cast<Builder::Align>(peek1);
|
|
||||||
ignore(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum State {
|
|
||||||
AfterAlign,
|
|
||||||
AfterSign,
|
|
||||||
AfterAlternativeForm,
|
|
||||||
AfterZeroPadding,
|
|
||||||
AfterWidth,
|
|
||||||
AfterDot,
|
|
||||||
AfterPrecision,
|
|
||||||
AfterType,
|
|
||||||
} state { State::AfterAlign };
|
|
||||||
|
|
||||||
size_t widthBegin = std::numeric_limits<size_t>::max();
|
|
||||||
size_t precisionBegin = std::numeric_limits<size_t>::max();
|
|
||||||
size_t widthEnd = 0;
|
|
||||||
size_t precisionEnd = 0;
|
|
||||||
std::string_view acceptedTypes = "bdoxaefgscpBXAFG";
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
char peek0 = peek();
|
|
||||||
|
|
||||||
if (peek0 == '}') {
|
|
||||||
ignore();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (isEOF()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign is only valid for numeric types
|
|
||||||
if (peek0 == '+' || peek0 == '-' || peek0 == ' ') {
|
|
||||||
VERIFY(state < State::AfterSign, "unexpected '%c' at this position", peek0);
|
|
||||||
state = State::AfterSign;
|
|
||||||
specifier.sign = static_cast<Builder::Sign>(peek0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alternative form is only valid for numeric types
|
|
||||||
if (peek0 == '#') {
|
|
||||||
VERIFY(state < State::AfterAlternativeForm, "unexpected '#' at this position");
|
|
||||||
state = State::AfterAlternativeForm;
|
|
||||||
specifier.alternativeForm = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign aware zero padding is only valid for numeric types
|
|
||||||
if (peek0 == '0') {
|
|
||||||
if (state < State::AfterWidth) {
|
|
||||||
VERIFY(state < State::AfterZeroPadding, "unexpected '0' at this position");
|
|
||||||
state = State::AfterZeroPadding;
|
|
||||||
specifier.zeroPadding = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peek0 >= '0' && peek0 <= '9') {
|
|
||||||
if (widthBegin == std::numeric_limits<size_t>::max() && state < State::AfterDot) {
|
|
||||||
VERIFY(state < State::AfterWidth, "unexpected '%c' at this position", peek0);
|
|
||||||
state = State::AfterWidth;
|
|
||||||
widthBegin = tell();
|
|
||||||
}
|
|
||||||
if (precisionBegin == std::numeric_limits<size_t>::max() && state == State::AfterDot) {
|
|
||||||
state = State::AfterPrecision;
|
|
||||||
precisionBegin = tell();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (peek0 == '.') {
|
|
||||||
if (state == State::AfterWidth) {
|
|
||||||
widthEnd = tell();
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY(state < State::AfterDot, "unexpected '.' at this position");
|
|
||||||
state = State::AfterDot;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((peek0 >= 'a' && peek0 <= 'z')
|
|
||||||
|| (peek0 >= 'A' && peek0 <= 'Z')) {
|
|
||||||
if (state == State::AfterWidth) {
|
|
||||||
widthEnd = tell();
|
|
||||||
}
|
|
||||||
if (state == State::AfterPrecision) {
|
|
||||||
precisionEnd = tell();
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY(state < State::AfterType, "unexpected '%c' at this position", peek0);
|
|
||||||
state = State::AfterType;
|
|
||||||
VERIFY(acceptedTypes.find(peek0) != std::string_view::npos, "unexpected '%c' at this position", peek0);
|
|
||||||
specifier.type = static_cast<PresentationType>(peek0);
|
|
||||||
}
|
|
||||||
|
|
||||||
ignore();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widthBegin != std::numeric_limits<size_t>::max()) {
|
|
||||||
if (widthEnd == 0) {
|
|
||||||
// We parse until after the closing '}', so take this into account
|
|
||||||
widthEnd = tell() - 1;
|
|
||||||
}
|
|
||||||
specifier.width = stringToNumber(m_input.substr(widthBegin, widthEnd - widthBegin));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (precisionBegin != std::numeric_limits<size_t>::max()) {
|
|
||||||
if (precisionEnd == 0) {
|
|
||||||
// We parse until after the closing '}', so take this into account
|
|
||||||
precisionEnd = tell() - 1;
|
|
||||||
}
|
|
||||||
specifier.precision = static_cast<int8_t>(stringToNumber(m_input.substr(precisionBegin, precisionEnd - precisionBegin)));
|
|
||||||
}
|
|
||||||
|
|
||||||
checkSpecifierType(specifier, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void Parser::checkSpecifierIntegralType(const Specifier& specifier)
|
|
||||||
{
|
|
||||||
switch (specifier.type) {
|
|
||||||
case PresentationType::None:
|
|
||||||
case PresentationType::Binary:
|
|
||||||
case PresentationType::BinaryUppercase:
|
|
||||||
case PresentationType::Character:
|
|
||||||
case PresentationType::Decimal:
|
|
||||||
case PresentationType::Octal:
|
|
||||||
case PresentationType::Hex:
|
|
||||||
case PresentationType::HexUppercase:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY(false, "invalid type specifier");
|
|
||||||
};
|
|
||||||
|
|
||||||
// Invalid: precision
|
|
||||||
VERIFY(specifier.precision == -1, "invalid specifier option");
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void Parser::checkSpecifierFloatingPointType(const Specifier& specifier)
|
|
||||||
{
|
|
||||||
switch (specifier.type) {
|
|
||||||
case PresentationType::Hexfloat:
|
|
||||||
case PresentationType::HexfloatUppercase:
|
|
||||||
case PresentationType::Exponent:
|
|
||||||
case PresentationType::ExponentUppercase:
|
|
||||||
case PresentationType::FixedPoint:
|
|
||||||
case PresentationType::FixedPointUppercase:
|
|
||||||
case PresentationType::General:
|
|
||||||
case PresentationType::GeneralUppercase:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY(false, "invalid type specifier");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void Parser::checkSpecifierCharType(const Specifier& specifier)
|
|
||||||
{
|
|
||||||
checkSpecifierIntegralType(specifier);
|
|
||||||
|
|
||||||
// Valid: fill, align, width
|
|
||||||
// Invalid: sign, alternativeForm, zeroPadding, precision
|
|
||||||
if (specifier.type == PresentationType::None
|
|
||||||
|| specifier.type == PresentationType::Character) {
|
|
||||||
VERIFY(specifier.sign == Builder::Sign::None, "invalid specifier option");
|
|
||||||
VERIFY(specifier.alternativeForm == false, "invalid specifier option");
|
|
||||||
VERIFY(specifier.zeroPadding == false, "invalid specifier option");
|
|
||||||
}
|
|
||||||
// Precision checked in Integral
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void Parser::checkSpecifierCStringType(const Specifier& specifier)
|
|
||||||
{
|
|
||||||
switch (specifier.type) {
|
|
||||||
case PresentationType::None:
|
|
||||||
case PresentationType::String:
|
|
||||||
case PresentationType::Pointer:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY(false, "invalid type specifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Valid: fill, align, width
|
|
||||||
// Invalid: sign, alternativeForm, zeroPadding, precision
|
|
||||||
VERIFY(specifier.sign == Builder::Sign::None, "invalid specifier option");
|
|
||||||
VERIFY(specifier.alternativeForm == false, "invalid specifier option");
|
|
||||||
VERIFY(specifier.zeroPadding == false, "invalid specifier option");
|
|
||||||
VERIFY(specifier.precision == -1, "invalid specifier option");
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void Parser::checkSpecifierStringType(const Specifier& specifier)
|
|
||||||
{
|
|
||||||
checkSpecifierCStringType(specifier);
|
|
||||||
VERIFY(specifier.type != PresentationType::Pointer, "invalid type specifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void Parser::checkSpecifierPointerType(const Specifier& specifier)
|
|
||||||
{
|
|
||||||
checkSpecifierCStringType(specifier);
|
|
||||||
VERIFY(specifier.type != PresentationType::String, "invalid type specifier");
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void Parser::checkSpecifierType(const Specifier& specifier, ParameterType type)
|
|
||||||
{
|
|
||||||
switch (type) {
|
|
||||||
case ParameterType::Integral:
|
|
||||||
checkSpecifierIntegralType(specifier);
|
|
||||||
break;
|
|
||||||
case ParameterType::FloatingPoint:
|
|
||||||
checkSpecifierFloatingPointType(specifier);
|
|
||||||
break;
|
|
||||||
case ParameterType::Char:
|
|
||||||
checkSpecifierCharType(specifier);
|
|
||||||
break;
|
|
||||||
case ParameterType::CString:
|
|
||||||
checkSpecifierCStringType(specifier);
|
|
||||||
break;
|
|
||||||
case ParameterType::String:
|
|
||||||
checkSpecifierStringType(specifier);
|
|
||||||
break;
|
|
||||||
case ParameterType::Pointer:
|
|
||||||
checkSpecifierPointerType(specifier);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
VERIFY_NOT_REACHED();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <optional>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/genericlexer.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
class Builder;
|
|
||||||
struct Specifier;
|
|
||||||
|
|
||||||
class Parser final : public GenericLexer {
|
|
||||||
public:
|
|
||||||
enum class ArgumentIndexingMode {
|
|
||||||
Automatic, // {} ,{}
|
|
||||||
Manual, // {0},{1}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ParameterType {
|
|
||||||
Integral,
|
|
||||||
FloatingPoint,
|
|
||||||
Char,
|
|
||||||
CString,
|
|
||||||
String,
|
|
||||||
Pointer,
|
|
||||||
};
|
|
||||||
|
|
||||||
Parser(std::string_view format, size_t parameterCount);
|
|
||||||
virtual ~Parser();
|
|
||||||
|
|
||||||
void checkFormatParameterConsistency();
|
|
||||||
size_t stringToNumber(std::string_view value);
|
|
||||||
|
|
||||||
std::string_view consumeLiteral();
|
|
||||||
std::optional<size_t> consumeIndex();
|
|
||||||
|
|
||||||
void parseSpecifier(Specifier& specifier, ParameterType type);
|
|
||||||
constexpr void checkSpecifierIntegralType(const Specifier& specifier);
|
|
||||||
constexpr void checkSpecifierFloatingPointType(const Specifier& specifier);
|
|
||||||
constexpr void checkSpecifierCharType(const Specifier& specifier);
|
|
||||||
constexpr void checkSpecifierCStringType(const Specifier& specifier);
|
|
||||||
constexpr void checkSpecifierStringType(const Specifier& specifier);
|
|
||||||
constexpr void checkSpecifierPointerType(const Specifier& specifier);
|
|
||||||
constexpr void checkSpecifierType(const Specifier& specifier, ParameterType type);
|
|
||||||
|
|
||||||
private:
|
|
||||||
ArgumentIndexingMode m_mode { ArgumentIndexingMode::Automatic };
|
|
||||||
size_t m_parameterCount { 0 };
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdio> // FILE, fputs, stdout, stderr
|
|
||||||
#include <iomanip> // setprecision
|
|
||||||
#include <ios> // defaultfloat, fixed
|
|
||||||
#include <sstream> // stringstream
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/print.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
void variadicPrint(FILE* file, std::string_view format, TypeErasedParameters& parameters)
|
|
||||||
{
|
|
||||||
std::stringstream stream;
|
|
||||||
variadicFormat(stream, format, parameters);
|
|
||||||
|
|
||||||
std::string string = stream.str();
|
|
||||||
fputs(string.c_str(), file);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
PrintOperatorStyle::PrintOperatorStyle(FILE* file)
|
|
||||||
: m_file(file)
|
|
||||||
, m_stream()
|
|
||||||
, m_builder(m_stream)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintOperatorStyle::~PrintOperatorStyle()
|
|
||||||
{
|
|
||||||
std::string string = m_stream.str();
|
|
||||||
fputs(string.c_str(), m_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintOperatorStyle print()
|
|
||||||
{
|
|
||||||
return PrintOperatorStyle(stdout);
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintOperatorStyle print(FILE* file)
|
|
||||||
{
|
|
||||||
return PrintOperatorStyle(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint> // uint8_t
|
|
||||||
#include <cstdio> // FILE, stdout
|
|
||||||
#include <sstream> // stringstream
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/format.h"
|
|
||||||
#include "util/timer.h"
|
|
||||||
|
|
||||||
namespace Util::Format {
|
|
||||||
|
|
||||||
void variadicPrint(FILE* file, std::string_view format, TypeErasedParameters& parameters);
|
|
||||||
|
|
||||||
template<size_t N, typename... Parameters>
|
|
||||||
void print(const char (&format)[N], const Parameters&... parameters)
|
|
||||||
{
|
|
||||||
VariadicParameters variadicParameters { parameters... };
|
|
||||||
variadicPrint(stdout, { format, N - 1 }, variadicParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<size_t N, typename... Parameters>
|
|
||||||
void print(FILE* file, const char (&format)[N], const Parameters&... parameters)
|
|
||||||
{
|
|
||||||
VariadicParameters variadicParameters { parameters... };
|
|
||||||
variadicPrint(file, { format, N - 1 }, variadicParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
class PrintOperatorStyle {
|
|
||||||
public:
|
|
||||||
PrintOperatorStyle(FILE* file);
|
|
||||||
virtual ~PrintOperatorStyle();
|
|
||||||
|
|
||||||
Builder& builder() { return m_builder; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
FILE* m_file;
|
|
||||||
|
|
||||||
std::stringstream m_stream;
|
|
||||||
Builder m_builder;
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
const PrintOperatorStyle& operator<<(const PrintOperatorStyle& printOperatorStyle, const T& value)
|
|
||||||
{
|
|
||||||
_format(const_cast<PrintOperatorStyle&>(printOperatorStyle).builder(), value);
|
|
||||||
return printOperatorStyle;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintOperatorStyle print();
|
|
||||||
PrintOperatorStyle print(FILE* file);
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
} // namespace Util::Format
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
using Util::Format::print;
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm> // max, min
|
|
||||||
|
|
||||||
#include "util/genericlexer.h"
|
|
||||||
#include "util/meta/assert.h"
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
GenericLexer::GenericLexer(std::string_view input)
|
|
||||||
: m_input(input)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
GenericLexer::~GenericLexer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
size_t GenericLexer::tell() const
|
|
||||||
{
|
|
||||||
return m_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t GenericLexer::tellRemaining() const
|
|
||||||
{
|
|
||||||
return m_input.length() - m_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GenericLexer::isEOF() const
|
|
||||||
{
|
|
||||||
return m_index >= m_input.length();
|
|
||||||
}
|
|
||||||
|
|
||||||
char GenericLexer::peek(size_t offset) const
|
|
||||||
{
|
|
||||||
return (m_index + offset < m_input.length()) ? m_input[m_index + offset] : '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenericLexer::ignore(size_t count)
|
|
||||||
{
|
|
||||||
m_index += std::min(count, m_input.length() - m_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenericLexer::retreat(size_t count)
|
|
||||||
{
|
|
||||||
m_index -= std::min(count, m_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
char GenericLexer::consume()
|
|
||||||
{
|
|
||||||
VERIFY(!isEOF());
|
|
||||||
return m_input[m_index++];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GenericLexer::consumeSpecific(const char& character)
|
|
||||||
{
|
|
||||||
if (peek() != character) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ignore();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
class GenericLexer {
|
|
||||||
public:
|
|
||||||
GenericLexer(std::string_view input);
|
|
||||||
virtual ~GenericLexer();
|
|
||||||
|
|
||||||
// Position
|
|
||||||
|
|
||||||
size_t tell() const;
|
|
||||||
size_t tellRemaining() const;
|
|
||||||
bool isEOF() const;
|
|
||||||
|
|
||||||
// Access
|
|
||||||
|
|
||||||
char peek(size_t offset = 0) const;
|
|
||||||
|
|
||||||
// Modifiers
|
|
||||||
|
|
||||||
void ignore(size_t count = 1);
|
|
||||||
void retreat(size_t count = 1);
|
|
||||||
char consume();
|
|
||||||
bool consumeSpecific(const char& character);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
size_t m_index { 0 };
|
|
||||||
std::string_view m_input;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "util/json/array.h"
|
|
||||||
#include "util/json/value.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
void Array::emplace_back(Value element)
|
|
||||||
{
|
|
||||||
m_elements.emplace_back(std::move(element));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value& Array::operator[](size_t index)
|
|
||||||
{
|
|
||||||
if (index + 1 > m_elements.size()) {
|
|
||||||
m_elements.resize(index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_elements[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <utility> // move
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util/json/parser.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
class Value;
|
|
||||||
|
|
||||||
class Array {
|
|
||||||
public:
|
|
||||||
Array() {}
|
|
||||||
virtual ~Array() {}
|
|
||||||
|
|
||||||
Array(const std::vector<Value>& elements)
|
|
||||||
: m_elements(elements)
|
|
||||||
{}
|
|
||||||
|
|
||||||
Array(const Array& other)
|
|
||||||
: m_elements(other.m_elements)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capacity
|
|
||||||
|
|
||||||
bool empty() const { return m_elements.empty(); }
|
|
||||||
size_t size() const { return m_elements.size(); }
|
|
||||||
void reserve(size_t size) { m_elements.reserve(size); }
|
|
||||||
|
|
||||||
// Element access
|
|
||||||
|
|
||||||
Value& operator[](size_t index);
|
|
||||||
|
|
||||||
Value& at(size_t index) { return m_elements.at(index); }
|
|
||||||
const Value& at(size_t index) const { return m_elements.at(index); }
|
|
||||||
|
|
||||||
const std::vector<Value>& elements() const { return m_elements; }
|
|
||||||
|
|
||||||
// Modifiers
|
|
||||||
|
|
||||||
void clear() { m_elements.clear(); }
|
|
||||||
void emplace_back(Value element);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<Value> m_elements;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm> // transform
|
|
||||||
#include <cstddef> // nullptr_t
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility> // forward
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util/json/array.h"
|
|
||||||
#include "util/json/object.h"
|
|
||||||
#include "util/meta/assert.h"
|
|
||||||
#include "util/meta/odr.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
namespace Detail {
|
|
||||||
|
|
||||||
// Required for containers with Json::Value type
|
|
||||||
template<typename Json>
|
|
||||||
void fromJson(const Json& json, Json& value)
|
|
||||||
{
|
|
||||||
value = json;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
void fromJson(const Json& json, std::nullptr_t& null)
|
|
||||||
{
|
|
||||||
VERIFY(json.type() == Json::Type::Null);
|
|
||||||
null = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
void fromJson(const Json& json, bool& boolean)
|
|
||||||
{
|
|
||||||
VERIFY(json.type() == Json::Type::Bool);
|
|
||||||
boolean = json.asBool();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
void fromJson(const Json& json, int& number)
|
|
||||||
{
|
|
||||||
VERIFY(json.type() == Json::Type::Number);
|
|
||||||
number = (int)json.asDouble();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
void fromJson(const Json& json, double& number)
|
|
||||||
{
|
|
||||||
VERIFY(json.type() == Json::Type::Number);
|
|
||||||
number = json.asDouble();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
void fromJson(const Json& json, std::string& string)
|
|
||||||
{
|
|
||||||
VERIFY(json.type() == Json::Type::String);
|
|
||||||
string = json.asString();
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json, typename T>
|
|
||||||
void fromJson(const Json& json, std::vector<T>& array)
|
|
||||||
{
|
|
||||||
VERIFY(json.type() == Json::Type::Array);
|
|
||||||
array.resize(json.size());
|
|
||||||
std::transform(
|
|
||||||
json.asArray().elements().begin(),
|
|
||||||
json.asArray().elements().end(),
|
|
||||||
array.begin(),
|
|
||||||
[](const Json& json) {
|
|
||||||
return json.template get<T>(); // (missing-dependent-template-keyword)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json, typename T>
|
|
||||||
void fromJson(const Json& json, std::map<std::string, T>& object)
|
|
||||||
{
|
|
||||||
VERIFY(json.type() == Json::Type::Object);
|
|
||||||
object.clear();
|
|
||||||
for (const auto& [name, value] : json.asObject().members()) {
|
|
||||||
object.emplace(name, value.template get<T>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json, typename T>
|
|
||||||
void fromJson(const Json& json, std::unordered_map<std::string, T>& object)
|
|
||||||
{
|
|
||||||
VERIFY(json.type() == Json::Type::Object);
|
|
||||||
object.clear();
|
|
||||||
for (const auto& [name, value] : json.asObject().members()) {
|
|
||||||
object.emplace(name, value.template get<T>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct fromJsonFunction {
|
|
||||||
template<typename Json, typename T>
|
|
||||||
auto operator()(const Json& json, T&& value) const
|
|
||||||
{
|
|
||||||
return fromJson(json, std::forward<T>(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Detail
|
|
||||||
|
|
||||||
// Anonymous namespace prevents multiple definition of the reference
|
|
||||||
namespace {
|
|
||||||
// Function object
|
|
||||||
constexpr const auto& fromJson = Util::Detail::staticConst<Detail::fromJsonFunction>; // NOLINT(misc-definitions-in-headers,clang-diagnostic-unused-variable)
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
|
|
||||||
// Customization Points
|
|
||||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
|
|
||||||
|
|
||||||
// Json::fromJson is a function object, the type of which is
|
|
||||||
// Json::Detail::fromJsonFunction. In the Json::Detail namespace are the
|
|
||||||
// fromJson free functions. The function call operator of fromJsonFunction makes
|
|
||||||
// an unqualified call to fromJson which, since it shares the Detail namespace
|
|
||||||
// with the fromJson free functions, will consider those in addition to any
|
|
||||||
// overloads that are found by argument-dependent lookup.
|
|
||||||
|
|
||||||
// Variable templates are linked externally, therefor every translation unit
|
|
||||||
// will see the same address for Detail::staticConst<Detail::fromJsonFunction>.
|
|
||||||
// Since Json::fromJson is a reference to the variable template, it too will
|
|
||||||
// have the same address in all translation units.
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm> // count
|
|
||||||
#include <sstream> // istringstream
|
|
||||||
#include <string> // getline
|
|
||||||
|
|
||||||
#include "util/json/job.h"
|
|
||||||
#include "util/json/lexer.h"
|
|
||||||
#include "util/json/parser.h"
|
|
||||||
#include "util/json/value.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
Job::Job(std::string_view input)
|
|
||||||
: m_input(input)
|
|
||||||
{
|
|
||||||
// FIXME: Make this work for all newline types: \n, \r, \r\n
|
|
||||||
m_lineNumbersWidth = std::count(m_input.begin(), m_input.end(), '\n');
|
|
||||||
m_lineNumbersWidth += m_input.back() == '\n' ? 0 : 1;
|
|
||||||
m_lineNumbersWidth = std::to_string(m_lineNumbersWidth).length();
|
|
||||||
}
|
|
||||||
|
|
||||||
Job::~Job()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------
|
|
||||||
|
|
||||||
Value Job::fire()
|
|
||||||
{
|
|
||||||
Lexer lexer(this);
|
|
||||||
lexer.analyze();
|
|
||||||
|
|
||||||
if (!m_success) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Parser parser(this);
|
|
||||||
Value value = parser.parse();
|
|
||||||
|
|
||||||
if (!m_success) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Job::printErrorLine(Token token, const char* message)
|
|
||||||
{
|
|
||||||
m_success = false;
|
|
||||||
|
|
||||||
// Error message
|
|
||||||
std::string errorFormat = "\033[;1m" // Bold
|
|
||||||
"JSON:%zu:%zu: "
|
|
||||||
"\033[31;1m" // Bold red
|
|
||||||
"error: "
|
|
||||||
"\033[0m" // Reset
|
|
||||||
"%s"
|
|
||||||
"\n";
|
|
||||||
fprintf(stderr,
|
|
||||||
errorFormat.c_str(),
|
|
||||||
token.line + 1,
|
|
||||||
token.column + 1,
|
|
||||||
message);
|
|
||||||
|
|
||||||
// Get the JSON line that caused the error
|
|
||||||
std::istringstream input(m_input.data());
|
|
||||||
std::string line;
|
|
||||||
for (size_t i = 0; std::getline(input, line); ++i) {
|
|
||||||
if (i == token.line) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Replace tab indentation with spaces
|
|
||||||
size_t oldLineLength = line.length();
|
|
||||||
size_t tabs = line.find_first_not_of('\t');
|
|
||||||
if (tabs > 0 && tabs < line.size()) {
|
|
||||||
line = std::string(tabs * 4, ' ') + line.substr(tabs);
|
|
||||||
}
|
|
||||||
token.column += line.length() - oldLineLength;
|
|
||||||
|
|
||||||
// JSON line
|
|
||||||
std::string lineFormat = " %"
|
|
||||||
+ std::to_string(m_lineNumbersWidth)
|
|
||||||
+ "zu | "
|
|
||||||
"%s"
|
|
||||||
"\033[31;1m" // Bold red
|
|
||||||
"%s"
|
|
||||||
"\033[0m" // Reset
|
|
||||||
"\n";
|
|
||||||
fprintf(stderr,
|
|
||||||
lineFormat.c_str(),
|
|
||||||
token.line + 1,
|
|
||||||
line.substr(0, token.column).c_str(),
|
|
||||||
line.substr(token.column).c_str());
|
|
||||||
|
|
||||||
// Arrow pointer
|
|
||||||
std::string arrowFormat = " %s | "
|
|
||||||
"\033[31;1m" // Bold red
|
|
||||||
"%s^%s"
|
|
||||||
"\033[0m" // Reset
|
|
||||||
"\n";
|
|
||||||
fprintf(stderr,
|
|
||||||
arrowFormat.c_str(),
|
|
||||||
std::string(m_lineNumbersWidth, ' ').c_str(),
|
|
||||||
std::string(token.column, ' ').c_str(),
|
|
||||||
std::string(line.length() - token.column, '~').c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util/json/lexer.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
class Value;
|
|
||||||
|
|
||||||
class Job {
|
|
||||||
public:
|
|
||||||
Job(std::string_view input);
|
|
||||||
virtual ~Job();
|
|
||||||
|
|
||||||
Value fire();
|
|
||||||
|
|
||||||
void printErrorLine(Token token, const char* message);
|
|
||||||
|
|
||||||
bool success() const { return m_success; }
|
|
||||||
std::string_view input() const { return m_input; }
|
|
||||||
std::vector<Token>* tokens() { return &m_tokens; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool m_success { true };
|
|
||||||
|
|
||||||
std::string_view m_input;
|
|
||||||
size_t m_lineNumbersWidth { 0 };
|
|
||||||
|
|
||||||
std::vector<Token> m_tokens;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "util/json/value.h"
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
using Json = Util::JSON::Value;
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "util/json/job.h"
|
|
||||||
#include "util/json/lexer.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
Lexer::Lexer(Job* job)
|
|
||||||
: GenericLexer(job->input())
|
|
||||||
, m_job(job)
|
|
||||||
, m_tokens(job->tokens())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Lexer::~Lexer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
void Lexer::analyze()
|
|
||||||
{
|
|
||||||
while (m_index < m_input.length()) {
|
|
||||||
switch (peek()) {
|
|
||||||
case '{':
|
|
||||||
m_tokens->push_back({ Token::Type::BraceOpen, m_line, m_column, "{" });
|
|
||||||
break;
|
|
||||||
case '}':
|
|
||||||
m_tokens->push_back({ Token::Type::BraceClose, m_line, m_column, "}" });
|
|
||||||
break;
|
|
||||||
case '[':
|
|
||||||
m_tokens->push_back({ Token::Type::BracketOpen, m_line, m_column, "[" });
|
|
||||||
break;
|
|
||||||
case ']':
|
|
||||||
m_tokens->push_back({ Token::Type::BracketClose, m_line, m_column, "]" });
|
|
||||||
break;
|
|
||||||
case ':':
|
|
||||||
m_tokens->push_back({ Token::Type::Colon, m_line, m_column, ":" });
|
|
||||||
break;
|
|
||||||
case ',':
|
|
||||||
m_tokens->push_back({ Token::Type::Comma, m_line, m_column, "," });
|
|
||||||
break;
|
|
||||||
case '"':
|
|
||||||
if (!consumeString()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case '-':
|
|
||||||
case '0':
|
|
||||||
case '1':
|
|
||||||
case '2':
|
|
||||||
case '3':
|
|
||||||
case '4':
|
|
||||||
case '5':
|
|
||||||
case '6':
|
|
||||||
case '7':
|
|
||||||
case '8':
|
|
||||||
case '9':
|
|
||||||
if (!consumeNumber()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
case 'b':
|
|
||||||
case 'c':
|
|
||||||
case 'd':
|
|
||||||
case 'e':
|
|
||||||
case 'f':
|
|
||||||
case 'g':
|
|
||||||
case 'h':
|
|
||||||
case 'i':
|
|
||||||
case 'j':
|
|
||||||
case 'k':
|
|
||||||
case 'l':
|
|
||||||
case 'm':
|
|
||||||
case 'n':
|
|
||||||
case 'o':
|
|
||||||
case 'p':
|
|
||||||
case 'q':
|
|
||||||
case 'r':
|
|
||||||
case 's':
|
|
||||||
case 't':
|
|
||||||
case 'u':
|
|
||||||
case 'v':
|
|
||||||
case 'w':
|
|
||||||
case 'x':
|
|
||||||
case 'y':
|
|
||||||
case 'z':
|
|
||||||
if (!consumeLiteral()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ' ':
|
|
||||||
case '\t':
|
|
||||||
break;
|
|
||||||
case '\r':
|
|
||||||
if (peek(1) == '\n') { // CRLF \r\n
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
m_column = -1;
|
|
||||||
m_line++;
|
|
||||||
break;
|
|
||||||
case '\n':
|
|
||||||
m_column = -1;
|
|
||||||
m_line++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Error!
|
|
||||||
m_tokens->push_back({ Token::Type::None, m_line, m_column, std::string(1, peek()) });
|
|
||||||
m_job->printErrorLine(m_tokens->back(),
|
|
||||||
(std::string() + "unexpected character '" + peek() + "'").c_str());
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ignore();
|
|
||||||
m_column++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
bool Lexer::consumeString()
|
|
||||||
{
|
|
||||||
size_t column = m_column;
|
|
||||||
std::string symbol = "";
|
|
||||||
|
|
||||||
bool escape = false;
|
|
||||||
char character = consume();
|
|
||||||
for (;;) {
|
|
||||||
character = peek();
|
|
||||||
|
|
||||||
if (!escape && character == '\\') {
|
|
||||||
symbol += '\\';
|
|
||||||
ignore();
|
|
||||||
escape = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!escape
|
|
||||||
&& (character == '"'
|
|
||||||
|| character == '\r'
|
|
||||||
|| character == '\n'
|
|
||||||
|| character == '\0')) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol += character;
|
|
||||||
ignore();
|
|
||||||
|
|
||||||
if (escape) {
|
|
||||||
escape = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_tokens->push_back({ Token::Type::String, m_line, column, symbol });
|
|
||||||
|
|
||||||
if (character != '"') {
|
|
||||||
m_job->printErrorLine(m_job->tokens()->back(), "strings should be wrapped in double quotes");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Lexer::consumeNumberOrLiteral(Token::Type type)
|
|
||||||
{
|
|
||||||
size_t index = m_index;
|
|
||||||
size_t column = m_column;
|
|
||||||
|
|
||||||
for (char character;;) {
|
|
||||||
character = peek();
|
|
||||||
|
|
||||||
if (character == '{'
|
|
||||||
|| character == '}'
|
|
||||||
|| character == '['
|
|
||||||
|| character == ']'
|
|
||||||
|| character == ':'
|
|
||||||
|| character == ','
|
|
||||||
|| character == '"'
|
|
||||||
|| character == ' '
|
|
||||||
|| character == '\t'
|
|
||||||
|| character == '\r'
|
|
||||||
|| character == '\n'
|
|
||||||
|| character == '\0') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ignore();
|
|
||||||
}
|
|
||||||
|
|
||||||
m_tokens->push_back({ type, m_line, column,
|
|
||||||
std::string(m_input.substr(index, m_index - index)) });
|
|
||||||
|
|
||||||
retreat();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Lexer::consumeNumber()
|
|
||||||
{
|
|
||||||
return consumeNumberOrLiteral(Token::Type::Number);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Lexer::consumeLiteral()
|
|
||||||
{
|
|
||||||
return consumeNumberOrLiteral(Token::Type::Literal);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// The JavaScript Object Notation (JSON) Data Interchange Format
|
|
||||||
// https://www.rfc-editor.org/rfc/pdfrfc/rfc8259.txt.pdf
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint8_t
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util/genericlexer.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
class Job;
|
|
||||||
|
|
||||||
struct Token {
|
|
||||||
enum class Type : uint8_t {
|
|
||||||
None,
|
|
||||||
BraceOpen, // {
|
|
||||||
BraceClose, // }
|
|
||||||
BracketOpen, // [
|
|
||||||
BracketClose, // ]
|
|
||||||
Colon, // :
|
|
||||||
Comma, // ,
|
|
||||||
String, // "foobar"
|
|
||||||
Number, // 123.456
|
|
||||||
Literal, // false/null/true (case sensitive)
|
|
||||||
};
|
|
||||||
|
|
||||||
Type type { Type::None };
|
|
||||||
size_t line { 0 };
|
|
||||||
size_t column { 0 };
|
|
||||||
std::string symbol;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Lexical analyzer
|
|
||||||
class Lexer final : public Util::GenericLexer {
|
|
||||||
public:
|
|
||||||
Lexer(Job* job);
|
|
||||||
virtual ~Lexer();
|
|
||||||
|
|
||||||
void analyze();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool consumeString();
|
|
||||||
bool consumeNumberOrLiteral(Token::Type type);
|
|
||||||
bool consumeNumber();
|
|
||||||
bool consumeLiteral();
|
|
||||||
|
|
||||||
Job* m_job { nullptr };
|
|
||||||
|
|
||||||
size_t m_column { 0 };
|
|
||||||
size_t m_line { 0 };
|
|
||||||
|
|
||||||
std::vector<Token>* m_tokens { nullptr };
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "util/json/object.h"
|
|
||||||
#include "util/json/value.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
void Object::emplace(const std::string& name, Value value)
|
|
||||||
{
|
|
||||||
m_members.emplace(name, std::move(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
Value& Object::operator[](const std::string& name)
|
|
||||||
{
|
|
||||||
if (m_members.find(name) == m_members.end()) {
|
|
||||||
emplace(name, {});
|
|
||||||
}
|
|
||||||
|
|
||||||
return m_members.at(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <utility> // move
|
|
||||||
|
|
||||||
#include "util/json/parser.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
class Value;
|
|
||||||
|
|
||||||
class Object {
|
|
||||||
public:
|
|
||||||
Object() {}
|
|
||||||
virtual ~Object() {}
|
|
||||||
|
|
||||||
Object(const Object& other)
|
|
||||||
: m_members(other.m_members)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capacity
|
|
||||||
|
|
||||||
bool empty() const { return m_members.empty(); }
|
|
||||||
size_t size() const { return m_members.size(); }
|
|
||||||
|
|
||||||
// Member access
|
|
||||||
|
|
||||||
Value& operator[](const std::string& name);
|
|
||||||
|
|
||||||
Value& at(const std::string& name) { return m_members.at(name); }
|
|
||||||
const Value& at(const std::string& name) const { return m_members.at(name); }
|
|
||||||
|
|
||||||
const std::map<std::string, Value>& members() const { return m_members; }
|
|
||||||
|
|
||||||
// Modifiers
|
|
||||||
|
|
||||||
void clear() { m_members.clear(); }
|
|
||||||
void emplace(const std::string& name, Value value);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::map<std::string, Value> m_members;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,497 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm> // count
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint8_t
|
|
||||||
#include <cstdio> // printf
|
|
||||||
#include <map>
|
|
||||||
#include <string> // stod
|
|
||||||
|
|
||||||
#include "util/json/array.h"
|
|
||||||
#include "util/json/job.h"
|
|
||||||
#include "util/json/lexer.h"
|
|
||||||
#include "util/json/object.h"
|
|
||||||
#include "util/json/parser.h"
|
|
||||||
#include "util/json/value.h"
|
|
||||||
#include "util/meta/assert.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
Parser::Parser(Job* job)
|
|
||||||
: m_job(job)
|
|
||||||
, m_tokens(m_job->tokens())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Parser::~Parser()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
Value Parser::parse()
|
|
||||||
{
|
|
||||||
Value result;
|
|
||||||
|
|
||||||
if (m_tokens->size() == 0) {
|
|
||||||
m_job->printErrorLine({}, "expecting token, not 'EOF'");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Token token = peek();
|
|
||||||
switch (token.type) {
|
|
||||||
case Token::Type::Literal:
|
|
||||||
result = consumeLiteral();
|
|
||||||
break;
|
|
||||||
case Token::Type::Number:
|
|
||||||
result = consumeNumber();
|
|
||||||
break;
|
|
||||||
case Token::Type::String:
|
|
||||||
result = consumeString();
|
|
||||||
break;
|
|
||||||
case Token::Type::BracketOpen:
|
|
||||||
result = consumeArray();
|
|
||||||
break;
|
|
||||||
case Token::Type::BraceOpen:
|
|
||||||
result = consumeObject();
|
|
||||||
break;
|
|
||||||
case Token::Type::BracketClose:
|
|
||||||
m_job->printErrorLine(token, "expecting value, not ']'");
|
|
||||||
m_index++;
|
|
||||||
break;
|
|
||||||
case Token::Type::BraceClose:
|
|
||||||
m_job->printErrorLine(token, "expecting string, not '}'");
|
|
||||||
m_index++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
m_job->printErrorLine(token, "multiple root elements");
|
|
||||||
m_index++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isEOF()) {
|
|
||||||
m_job->printErrorLine(peek(), "multiple root elements");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
bool Parser::isEOF()
|
|
||||||
{
|
|
||||||
return m_index >= m_tokens->size();
|
|
||||||
}
|
|
||||||
|
|
||||||
Token Parser::peek()
|
|
||||||
{
|
|
||||||
VERIFY(!isEOF());
|
|
||||||
return (*m_tokens)[m_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
Token Parser::consume()
|
|
||||||
{
|
|
||||||
VERIFY(!isEOF());
|
|
||||||
return (*m_tokens)[m_index++];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Parser::ignoreUntil(Token::Type type)
|
|
||||||
{
|
|
||||||
while (!isEOF() && peek().type != type) {
|
|
||||||
++m_index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Parser::consumeLiteral()
|
|
||||||
{
|
|
||||||
Token token = consume();
|
|
||||||
|
|
||||||
if (token.symbol == "null") {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
else if (token.symbol == "true") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (token.symbol == "false") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_job->printErrorLine(token, "invalid literal");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Parser::consumeNumber()
|
|
||||||
{
|
|
||||||
Token token = consume();
|
|
||||||
|
|
||||||
auto reportError = [this](Token token, const std::string& message) -> void {
|
|
||||||
m_job->printErrorLine(token, message.c_str());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
// number = [ minus ] int [ frac ] [ exp ]
|
|
||||||
|
|
||||||
size_t minusPrefix = token.symbol[0] == '-' ? 1 : 0;
|
|
||||||
|
|
||||||
// Leading 0s
|
|
||||||
if (token.symbol[minusPrefix] == '0'
|
|
||||||
&& token.symbol[minusPrefix + 1] > '0' && token.symbol[minusPrefix + 1] < '9') {
|
|
||||||
reportError(token, "invalid leading zero");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class State : uint8_t {
|
|
||||||
Int,
|
|
||||||
Fraction,
|
|
||||||
Exponent
|
|
||||||
};
|
|
||||||
|
|
||||||
State state = State::Int;
|
|
||||||
|
|
||||||
#define CHECK_IF_VALID_NUMBER \
|
|
||||||
if (character < 48 || character > 57) { \
|
|
||||||
reportError(token, std::string() + "invalid number, unexpected '" + character + '\''); \
|
|
||||||
return nullptr; \
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t fractionPosition = 0;
|
|
||||||
size_t exponentPosition = 0;
|
|
||||||
size_t length = token.symbol.length();
|
|
||||||
for (size_t i = 0; i < length; ++i) {
|
|
||||||
char character = token.symbol[i];
|
|
||||||
|
|
||||||
// Int -> Fraction
|
|
||||||
if (character == '.' && state == State::Int) {
|
|
||||||
state = State::Fraction;
|
|
||||||
fractionPosition = i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Int/Fraction -> Exponent
|
|
||||||
else if ((character == 'e' || character == 'E') && state != State::Exponent) {
|
|
||||||
state = State::Exponent;
|
|
||||||
exponentPosition = i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == State::Int) {
|
|
||||||
if (character == '-') {
|
|
||||||
if (i == length - 1) {
|
|
||||||
reportError(token, "expected number after minus");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (i != 0) {
|
|
||||||
reportError(token, "invalid minus");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
CHECK_IF_VALID_NUMBER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (state == State::Fraction) {
|
|
||||||
CHECK_IF_VALID_NUMBER;
|
|
||||||
}
|
|
||||||
else if (state == State::Exponent) {
|
|
||||||
if (character == '-' || character == '+') {
|
|
||||||
if (i == length - 1) {
|
|
||||||
reportError(token, "expected number after plus/minus");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (i > exponentPosition + 1) {
|
|
||||||
reportError(token, "invalid plus/minus");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
CHECK_IF_VALID_NUMBER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fractionPosition != 0 || exponentPosition != 0) {
|
|
||||||
if (fractionPosition == exponentPosition - 1) {
|
|
||||||
reportError(token, "invalid exponent sign, expected number");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fractionPosition == length - 1 || exponentPosition == length - 1) {
|
|
||||||
reportError(token, "invalid number");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::stod(token.symbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Parser::consumeString()
|
|
||||||
{
|
|
||||||
Token token = consume();
|
|
||||||
|
|
||||||
auto reportError = [this](Token token, const std::string& message) -> void {
|
|
||||||
m_job->printErrorLine(token, message.c_str());
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME: support \u Unicode character escape sequence
|
|
||||||
auto getPrintableString = [](char character) -> std::string {
|
|
||||||
if (character == '"' || character == '\\' || character == '/'
|
|
||||||
|| (character >= 0 && character <= 31)) {
|
|
||||||
switch (character) {
|
|
||||||
case '"':
|
|
||||||
return "\\\"";
|
|
||||||
break;
|
|
||||||
case '\\':
|
|
||||||
return "\\\\";
|
|
||||||
break;
|
|
||||||
case '/':
|
|
||||||
return "/";
|
|
||||||
break;
|
|
||||||
case '\b':
|
|
||||||
return "\\b";
|
|
||||||
break;
|
|
||||||
case '\f':
|
|
||||||
return "\\f";
|
|
||||||
break;
|
|
||||||
case '\n':
|
|
||||||
return "\\n";
|
|
||||||
break;
|
|
||||||
case '\r':
|
|
||||||
return "\\r";
|
|
||||||
break;
|
|
||||||
case '\t':
|
|
||||||
return "\\t";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
char buffer[7];
|
|
||||||
sprintf(buffer, "\\u%0.4X", character);
|
|
||||||
return std::string(buffer);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::string() + character;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string string;
|
|
||||||
|
|
||||||
bool escape = false;
|
|
||||||
for (char character : token.symbol) {
|
|
||||||
if (!escape) {
|
|
||||||
if (character == '\\') {
|
|
||||||
escape = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (character == '"' || (character >= 0 && character <= 31)) {
|
|
||||||
reportError(token, "invalid string, unescaped character found");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
string += getPrintableString(character);
|
|
||||||
|
|
||||||
if (escape) {
|
|
||||||
escape = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Parser::consumeArray()
|
|
||||||
{
|
|
||||||
m_index++;
|
|
||||||
|
|
||||||
auto reportError = [this](Token token, const std::string& message) -> void {
|
|
||||||
m_job->printErrorLine(token, message.c_str());
|
|
||||||
|
|
||||||
// After an error, try to find the closing bracket
|
|
||||||
ignoreUntil(Token::Type::BracketClose);
|
|
||||||
m_index++;
|
|
||||||
};
|
|
||||||
|
|
||||||
Value array = Value::Type::Array;
|
|
||||||
Token token;
|
|
||||||
for (;;) {
|
|
||||||
// EOF
|
|
||||||
if (isEOF()) {
|
|
||||||
reportError(m_tokens->at(m_index - 1), "expecting closing ']' at end");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
token = peek();
|
|
||||||
if (token.type == Token::Type::Literal) {
|
|
||||||
array.emplace_back(consumeLiteral());
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::Number) {
|
|
||||||
array.emplace_back(consumeNumber());
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::String) {
|
|
||||||
array.emplace_back(consumeString());
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::BracketOpen) {
|
|
||||||
array.emplace_back(consumeArray());
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::BraceOpen) {
|
|
||||||
array.emplace_back(consumeObject());
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::BracketClose) {
|
|
||||||
// Trailing comma
|
|
||||||
if (array.m_value.array->size() > 0) {
|
|
||||||
reportError(m_tokens->at(m_index - 1), "invalid comma, expecting ']'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reportError(token, "expecting value or ']', not '" + token.symbol + "'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EOF
|
|
||||||
if (isEOF()) {
|
|
||||||
reportError(token, "expecting closing ']' at end");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find , or ]
|
|
||||||
token = consume();
|
|
||||||
if (token.type == Token::Type::Comma) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::BracketClose) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reportError(m_tokens->at(m_index - 1), "expecting comma or ']', not '" + token.symbol + "'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Parser::consumeObject()
|
|
||||||
{
|
|
||||||
m_index++;
|
|
||||||
|
|
||||||
auto reportError = [this](Token token, const std::string& message) -> void {
|
|
||||||
m_job->printErrorLine(token, message.c_str());
|
|
||||||
|
|
||||||
// After an error, try to find the closing brace
|
|
||||||
ignoreUntil(Token::Type::BraceClose);
|
|
||||||
m_index++;
|
|
||||||
};
|
|
||||||
|
|
||||||
Value object = Value::Type::Object;
|
|
||||||
Token token;
|
|
||||||
std::string name;
|
|
||||||
std::map<std::string, uint8_t> unique;
|
|
||||||
for (;;) {
|
|
||||||
// EOF
|
|
||||||
if (isEOF()) {
|
|
||||||
reportError(m_tokens->at(m_index - 1), "expecting closing '}' at end");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
token = consume();
|
|
||||||
if (token.type == Token::Type::BraceClose) {
|
|
||||||
// Trailing comma
|
|
||||||
if (object.m_value.object->size() > 0) {
|
|
||||||
reportError(m_tokens->at(m_index - 1), "invalid comma, expecting '}'");
|
|
||||||
}
|
|
||||||
// Empty object
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (token.type != Token::Type::String) {
|
|
||||||
reportError(token, "expecting string or '}', not '" + token.symbol + "'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find member name
|
|
||||||
m_index--;
|
|
||||||
Value tmpName = consumeString();
|
|
||||||
if (tmpName.m_type != Value::Type::String) {
|
|
||||||
ignoreUntil(Token::Type::BraceClose);
|
|
||||||
m_index++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if name exists in hashmap
|
|
||||||
name = *tmpName.m_value.string;
|
|
||||||
if (unique.find(name) != unique.end()) {
|
|
||||||
reportError(token, "duplicate name '" + token.symbol + "', names should be unique");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Add name to hashmap
|
|
||||||
unique.insert({ name, 0 });
|
|
||||||
|
|
||||||
// EOF
|
|
||||||
if (isEOF()) {
|
|
||||||
reportError(token, "expecting colon, not 'EOF'");
|
|
||||||
reportError(token, "expecting closing '}' at end");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find :
|
|
||||||
token = consume();
|
|
||||||
if (token.type != Token::Type::Colon) {
|
|
||||||
reportError(token, "expecting colon, not '" + token.symbol + "'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EOF
|
|
||||||
if (isEOF()) {
|
|
||||||
reportError(token, "expecting value, not 'EOF'");
|
|
||||||
reportError(token, "expecting closing '}' at end");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add member (name:value pair) to object
|
|
||||||
token = peek();
|
|
||||||
if (token.type == Token::Type::Literal) {
|
|
||||||
object.emplace(name, consumeLiteral());
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::Number) {
|
|
||||||
object.emplace(name, consumeNumber());
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::String) {
|
|
||||||
object.emplace(name, consumeString());
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::BracketOpen) {
|
|
||||||
object.emplace(name, consumeArray());
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::BraceOpen) {
|
|
||||||
object.emplace(name, consumeObject());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reportError(token, "expecting value, not '" + token.symbol + "'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EOF
|
|
||||||
if (isEOF()) {
|
|
||||||
reportError(token, "expecting closing '}' at end");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find , or }
|
|
||||||
token = consume();
|
|
||||||
if (token.type == Token::Type::Comma) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (token.type == Token::Type::BraceClose) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
reportError(token, "expecting comma or '}', not '" + token.symbol + "'");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util/json/lexer.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
class Job;
|
|
||||||
class Value;
|
|
||||||
|
|
||||||
class Parser {
|
|
||||||
public:
|
|
||||||
Parser(Job* job);
|
|
||||||
virtual ~Parser();
|
|
||||||
|
|
||||||
Value parse();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool isEOF();
|
|
||||||
Token peek();
|
|
||||||
Token consume();
|
|
||||||
void ignoreUntil(Token::Type type);
|
|
||||||
|
|
||||||
Value consumeLiteral();
|
|
||||||
Value consumeNumber();
|
|
||||||
Value consumeString();
|
|
||||||
Value consumeArray();
|
|
||||||
Value consumeObject();
|
|
||||||
|
|
||||||
Job* m_job { nullptr };
|
|
||||||
|
|
||||||
size_t m_index { 0 };
|
|
||||||
|
|
||||||
std::vector<Token>* m_tokens { nullptr };
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstdint> // uint32_t
|
|
||||||
#include <iterator> // next
|
|
||||||
#include <sstream> // ostringstream
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "util/json/array.h"
|
|
||||||
#include "util/json/lexer.h"
|
|
||||||
#include "util/json/object.h"
|
|
||||||
#include "util/json/serializer.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
Serializer::Serializer(const uint32_t indent, const char indentCharacter)
|
|
||||||
: m_indent(indent)
|
|
||||||
, m_indentCharacter(indentCharacter)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Serializer::~Serializer()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------
|
|
||||||
|
|
||||||
std::string Serializer::dump(const Value& value)
|
|
||||||
{
|
|
||||||
dumpHelper(value);
|
|
||||||
return m_output;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------
|
|
||||||
|
|
||||||
void Serializer::dumpHelper(const Value& value, const uint32_t indentLevel)
|
|
||||||
{
|
|
||||||
switch (value.m_type) {
|
|
||||||
case Value::Type::Null:
|
|
||||||
m_output += "null";
|
|
||||||
break;
|
|
||||||
case Value::Type::Bool:
|
|
||||||
m_output += value.m_value.boolean ? "true" : "false";
|
|
||||||
break;
|
|
||||||
case Value::Type::Number: {
|
|
||||||
std::ostringstream os;
|
|
||||||
os << value.m_value.number;
|
|
||||||
m_output += os.str();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Value::Type::String:
|
|
||||||
m_output += "\"" + *value.m_value.string + "\"";
|
|
||||||
break;
|
|
||||||
case Value::Type::Array:
|
|
||||||
dumpArray(value, indentLevel);
|
|
||||||
break;
|
|
||||||
case Value::Type::Object:
|
|
||||||
dumpObject(value, indentLevel);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serializer::dumpArray(const Value& value, const uint32_t indentLevel)
|
|
||||||
{
|
|
||||||
if (m_indent > 0) {
|
|
||||||
m_output += '[';
|
|
||||||
m_output += '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty Array early return
|
|
||||||
if (value.m_value.array->empty()) {
|
|
||||||
m_output += ']';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
auto it = value.m_value.array->elements().cbegin();
|
|
||||||
if (!m_indent) {
|
|
||||||
for (; i < value.m_value.array->size() - 1; ++i, ++it) {
|
|
||||||
dumpHelper(*it, indentLevel + 1);
|
|
||||||
m_output += ',';
|
|
||||||
}
|
|
||||||
dumpHelper(*it, indentLevel + 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::string indentation = std::string(m_indent * (indentLevel + 1), m_indentCharacter);
|
|
||||||
|
|
||||||
for (; i < value.m_value.array->size() - 1; ++i, ++it) {
|
|
||||||
m_output += indentation;
|
|
||||||
dumpHelper(*it, indentLevel + 1);
|
|
||||||
m_output += ",\n";
|
|
||||||
}
|
|
||||||
m_output += indentation;
|
|
||||||
dumpHelper(*it, indentLevel + 1);
|
|
||||||
m_output += '\n';
|
|
||||||
|
|
||||||
// Append indentation
|
|
||||||
m_output += std::string(m_indent * indentLevel, m_indentCharacter);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_output += "]";
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serializer::dumpObject(const Value& value, const uint32_t indentLevel)
|
|
||||||
{
|
|
||||||
if (m_indent > 0) {
|
|
||||||
m_output += '{';
|
|
||||||
m_output += '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty Object early return
|
|
||||||
if (value.m_value.object->empty()) {
|
|
||||||
m_output += '}';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t i = 0;
|
|
||||||
auto it = value.m_value.object->members().cbegin();
|
|
||||||
if (!m_indent) {
|
|
||||||
for (; i < value.m_value.object->size() - 1; ++i, ++it) {
|
|
||||||
m_output += '"' + it->first + "\":";
|
|
||||||
dumpHelper(it->second, indentLevel + 1);
|
|
||||||
m_output += ',';
|
|
||||||
}
|
|
||||||
m_output += '"' + it->first + "\":";
|
|
||||||
dumpHelper(it->second, indentLevel + 1);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
std::string indentation = std::string(m_indent * (indentLevel + 1), m_indentCharacter);
|
|
||||||
|
|
||||||
for (; i < value.m_value.object->size() - 1; ++i, ++it) {
|
|
||||||
m_output += indentation;
|
|
||||||
m_output += '"' + it->first + "\": ";
|
|
||||||
dumpHelper(it->second, indentLevel + 1);
|
|
||||||
m_output += ",\n";
|
|
||||||
}
|
|
||||||
m_output += indentation;
|
|
||||||
m_output += '"' + it->first + "\": ";
|
|
||||||
dumpHelper(it->second, indentLevel + 1);
|
|
||||||
m_output += '\n';
|
|
||||||
|
|
||||||
// Append indentation
|
|
||||||
m_output += std::string(m_indent * indentLevel, m_indentCharacter);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_output += '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint> // uint32_t
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include "util/json/value.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
class Serializer {
|
|
||||||
public:
|
|
||||||
Serializer(const uint32_t indent = 0, const char indentCharacter = ' ');
|
|
||||||
virtual ~Serializer();
|
|
||||||
|
|
||||||
std::string dump(const Value& value);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void dumpHelper(const Value& value, const uint32_t indentLevel = 0);
|
|
||||||
void dumpArray(const Value& value, const uint32_t indentLevel = 0);
|
|
||||||
void dumpObject(const Value& value, const uint32_t indentLevel = 0);
|
|
||||||
|
|
||||||
std::string m_output;
|
|
||||||
|
|
||||||
uint32_t m_indent { 0 };
|
|
||||||
char m_indentCharacter { ' ' };
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cassert> // assert
|
|
||||||
#include <cstddef> // nullptr_t
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility> // forward
|
|
||||||
|
|
||||||
#include "util/json/array.h"
|
|
||||||
#include "util/json/object.h"
|
|
||||||
#include "util/meta/odr.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
namespace Detail {
|
|
||||||
|
|
||||||
struct jsonConstructor {
|
|
||||||
template<typename Json>
|
|
||||||
static void construct(Json& json, bool boolean)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::Bool;
|
|
||||||
json.m_value.boolean = boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
static void construct(Json& json, int number)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::Number;
|
|
||||||
json.m_value.number = (double)number;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
static void construct(Json& json, double number)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::Number;
|
|
||||||
json.m_value.number = number;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
static void construct(Json& json, const char* string)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::String;
|
|
||||||
json.m_value.string = new std::string(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
static void construct(Json& json, const std::string& string)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::String;
|
|
||||||
json.m_value.string = new std::string(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
static void construct(Json& json, const Array& array)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::Array;
|
|
||||||
json.m_value.array = new Array(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json, typename T>
|
|
||||||
static void construct(Json& json, const std::vector<T>& array)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::Array;
|
|
||||||
json.m_value.array = new Array;
|
|
||||||
json.m_value.array->reserve(array.size());
|
|
||||||
for (const T& value : array) {
|
|
||||||
json.m_value.array->emplace_back(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json>
|
|
||||||
static void construct(Json& json, const Object& object)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::Object;
|
|
||||||
json.m_value.object = new Object(object);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json, typename T>
|
|
||||||
static void construct(Json& json, const std::map<std::string, T>& object)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::Object;
|
|
||||||
json.m_value.object = new Object;
|
|
||||||
for (const auto& [name, value] : object) {
|
|
||||||
json.m_value.object->emplace(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Json, typename T>
|
|
||||||
static void construct(Json& json, const std::unordered_map<std::string, T>& object)
|
|
||||||
{
|
|
||||||
json.destroy();
|
|
||||||
json.m_type = Json::Type::Object;
|
|
||||||
json.m_value.object = new Object;
|
|
||||||
for (const auto& [name, value] : object) {
|
|
||||||
json.m_value.object->emplace(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename Json, typename T>
|
|
||||||
void toJson(Json& json, const T& value)
|
|
||||||
{
|
|
||||||
jsonConstructor::construct(json, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct toJsonFunction {
|
|
||||||
template<typename Json, typename T>
|
|
||||||
auto operator()(Json& json, T&& value) const
|
|
||||||
{
|
|
||||||
return toJson(json, std::forward<T>(value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Detail
|
|
||||||
|
|
||||||
// Anonymous namespace prevents multiple definition of the reference
|
|
||||||
namespace {
|
|
||||||
// Function object
|
|
||||||
constexpr const auto& toJson = Util::Detail::staticConst<Detail::toJsonFunction>; // NOLINT(misc-definitions-in-headers,clang-diagnostic-unused-variable)
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
|
|
||||||
// Customization Points
|
|
||||||
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html
|
|
||||||
|
|
||||||
// Json::toJson is a function object, the type of which is
|
|
||||||
// Json::Detail::toJsonFunction. In the Json::Detail namespace are the toJson
|
|
||||||
// free functions. The function call operator of toJsonFunction makes an
|
|
||||||
// unqualified call to toJson which, since it shares the Detail namespace with
|
|
||||||
// the toJson free functions, will consider those in addition to any overloads
|
|
||||||
// that are found by argument-dependent lookup.
|
|
||||||
|
|
||||||
// Variable templates are linked externally, therefor every translation unit
|
|
||||||
// will see the same address for Detail::staticConst<Detail::toJsonFunction>.
|
|
||||||
// Since Json::toJson is a reference to the variable template, it too will have
|
|
||||||
// the same address in all translation units.
|
|
||||||
@@ -1,340 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm> // all_of
|
|
||||||
#include <cstdint> // uint32_t
|
|
||||||
#include <fstream> // >>
|
|
||||||
#include <iostream> // istream, ostream
|
|
||||||
#include <string>
|
|
||||||
#include <utility> // move, swap
|
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
|
||||||
#include "util/json/array.h"
|
|
||||||
#include "util/json/job.h"
|
|
||||||
#include "util/json/object.h"
|
|
||||||
#include "util/json/serializer.h"
|
|
||||||
#include "util/json/value.h"
|
|
||||||
#include "util/meta/assert.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
Value::Value(std::nullptr_t)
|
|
||||||
: Value(Type::Null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Value(Type type)
|
|
||||||
: m_type(type)
|
|
||||||
{
|
|
||||||
switch (m_type) {
|
|
||||||
case Type::Bool:
|
|
||||||
m_value.boolean = false;
|
|
||||||
break;
|
|
||||||
case Type::Number:
|
|
||||||
m_value.number = 0.0;
|
|
||||||
break;
|
|
||||||
case Type::String:
|
|
||||||
m_value.string = new std::string;
|
|
||||||
break;
|
|
||||||
case Type::Array:
|
|
||||||
m_value.array = new Array;
|
|
||||||
break;
|
|
||||||
case Type::Object:
|
|
||||||
m_value.object = new Object;
|
|
||||||
break;
|
|
||||||
case Type::Null:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Value(const std::initializer_list<Value>& values)
|
|
||||||
{
|
|
||||||
bool isObject = std::all_of(values.begin(), values.end(), [](const Value& value) {
|
|
||||||
return value.type() == Type::Array
|
|
||||||
&& value.size() == 2
|
|
||||||
&& value[0].m_type == Type::String;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isObject) {
|
|
||||||
m_type = Type::Array;
|
|
||||||
m_value.array = new Array(values);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_type = Type::Object;
|
|
||||||
m_value.object = new Object;
|
|
||||||
|
|
||||||
for (auto& value : values) {
|
|
||||||
m_value.object->emplace(std::move(*value[0].m_value.string),
|
|
||||||
std::move(value[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy constructor
|
|
||||||
Value::Value(const Value& other)
|
|
||||||
: m_type(other.m_type)
|
|
||||||
{
|
|
||||||
switch (m_type) {
|
|
||||||
case Type::Bool:
|
|
||||||
m_value.boolean = other.m_value.boolean;
|
|
||||||
break;
|
|
||||||
case Type::Number:
|
|
||||||
m_value.number = other.m_value.number;
|
|
||||||
break;
|
|
||||||
case Type::String:
|
|
||||||
m_value.string = new std::string(*other.m_value.string);
|
|
||||||
break;
|
|
||||||
case Type::Array:
|
|
||||||
m_value.array = new Array(*other.m_value.array);
|
|
||||||
break;
|
|
||||||
case Type::Object:
|
|
||||||
m_value.object = new Object(*other.m_value.object);
|
|
||||||
break;
|
|
||||||
case Type::Null:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move constructor
|
|
||||||
Value::Value(Value&& other) noexcept
|
|
||||||
: Value(Type::Null) // Initialize via default construction
|
|
||||||
{
|
|
||||||
// Allow std::swap as a fallback on ADL failure
|
|
||||||
using std::swap;
|
|
||||||
// Unqualified call to swap, allow ADL to operate and find best match
|
|
||||||
swap(*this, other);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy assignment
|
|
||||||
// Move assignment
|
|
||||||
Value& Value::operator=(Value other)
|
|
||||||
{
|
|
||||||
// Allow std::swap as a fallback on ADL failure
|
|
||||||
using std::swap;
|
|
||||||
// Unqualified call to swap, allow ADL to operate and find best match
|
|
||||||
swap(*this, other);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void swap(Value& left, Value& right) noexcept
|
|
||||||
{
|
|
||||||
std::swap(left.m_type, right.m_type);
|
|
||||||
std::swap(left.m_value, right.m_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------
|
|
||||||
|
|
||||||
void Value::clear()
|
|
||||||
{
|
|
||||||
switch (m_type) {
|
|
||||||
case Type::Bool:
|
|
||||||
m_value.boolean = false;
|
|
||||||
break;
|
|
||||||
case Type::Number:
|
|
||||||
m_value.number = 0.0;
|
|
||||||
break;
|
|
||||||
case Type::String:
|
|
||||||
m_value.string->clear();
|
|
||||||
break;
|
|
||||||
case Type::Array:
|
|
||||||
m_value.array->clear();
|
|
||||||
break;
|
|
||||||
case Type::Object:
|
|
||||||
m_value.object->clear();
|
|
||||||
break;
|
|
||||||
case Type::Null:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Value::parse(std::string_view input)
|
|
||||||
{
|
|
||||||
return Job(input).fire();
|
|
||||||
}
|
|
||||||
|
|
||||||
Value Value::parse(std::ifstream& file)
|
|
||||||
{
|
|
||||||
Value value;
|
|
||||||
file >> value;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Value::dump(const uint32_t indent, const char indentCharacter) const
|
|
||||||
{
|
|
||||||
Serializer serializer(indent, indentCharacter);
|
|
||||||
return serializer.dump(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Value::emplace_back(Value value)
|
|
||||||
{
|
|
||||||
// Implicitly convert null to an array
|
|
||||||
if (m_type == Type::Null) {
|
|
||||||
m_type = Type::Array;
|
|
||||||
m_value.array = new Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY(m_type == Type::Array);
|
|
||||||
m_value.array->emplace_back(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Value::emplace(const std::string& key, Value value)
|
|
||||||
{
|
|
||||||
// Implicitly convert null to an object
|
|
||||||
if (m_type == Type::Null) {
|
|
||||||
m_type = Type::Object;
|
|
||||||
m_value.object = new Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY(m_type == Type::Object);
|
|
||||||
m_value.object->emplace(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Value::exists(size_t index) const
|
|
||||||
{
|
|
||||||
return index < size();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Value::exists(const std::string& key) const
|
|
||||||
{
|
|
||||||
VERIFY(m_type == Type::Object);
|
|
||||||
return m_value.object->members().find(key) != m_value.object->members().end();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------
|
|
||||||
|
|
||||||
Value& Value::operator[](size_t index)
|
|
||||||
{
|
|
||||||
// Implicitly convert null to an array
|
|
||||||
if (m_type == Type::Null) {
|
|
||||||
m_type = Type::Array;
|
|
||||||
m_value.array = new Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY(m_type == Type::Array);
|
|
||||||
return (*m_value.array)[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
Value& Value::operator[](const std::string& key)
|
|
||||||
{
|
|
||||||
// Implicitly convert null to an object
|
|
||||||
if (m_type == Type::Null) {
|
|
||||||
m_type = Type::Object;
|
|
||||||
m_value.object = new Object;
|
|
||||||
}
|
|
||||||
|
|
||||||
VERIFY(m_type == Type::Object);
|
|
||||||
return (*m_value.object)[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
const Value& Value::operator[](size_t index) const
|
|
||||||
{
|
|
||||||
VERIFY(m_type == Type::Array);
|
|
||||||
return (*m_value.array)[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
const Value& Value::operator[](const std::string& key) const
|
|
||||||
{
|
|
||||||
VERIFY(m_type == Type::Object);
|
|
||||||
return (*m_value.object)[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
Value& Value::at(size_t index)
|
|
||||||
{
|
|
||||||
VERIFY(m_type == Type::Array);
|
|
||||||
return m_value.array->at(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
Value& Value::at(const std::string& key)
|
|
||||||
{
|
|
||||||
VERIFY(m_type == Type::Object);
|
|
||||||
return m_value.object->at(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Value& Value::at(size_t index) const
|
|
||||||
{
|
|
||||||
VERIFY(m_type == Type::Array);
|
|
||||||
return m_value.array->at(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
const Value& Value::at(const std::string& key) const
|
|
||||||
{
|
|
||||||
VERIFY(m_type == Type::Object);
|
|
||||||
return m_value.object->at(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------
|
|
||||||
|
|
||||||
size_t Value::size() const
|
|
||||||
{
|
|
||||||
switch (m_type) {
|
|
||||||
case Type::Null:
|
|
||||||
return 0;
|
|
||||||
case Type::Array:
|
|
||||||
return m_value.array->size();
|
|
||||||
case Type::Object:
|
|
||||||
return m_value.object->size();
|
|
||||||
case Type::Bool:
|
|
||||||
case Type::Number:
|
|
||||||
case Type::String:
|
|
||||||
default:
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------
|
|
||||||
|
|
||||||
void Value::destroy()
|
|
||||||
{
|
|
||||||
switch (m_type) {
|
|
||||||
case Type::String:
|
|
||||||
delete m_value.string;
|
|
||||||
break;
|
|
||||||
case Type::Array:
|
|
||||||
delete m_value.array;
|
|
||||||
break;
|
|
||||||
case Type::Object:
|
|
||||||
delete m_value.object;
|
|
||||||
break;
|
|
||||||
case Type::Null:
|
|
||||||
case Type::Bool:
|
|
||||||
case Type::Number:
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------
|
|
||||||
|
|
||||||
std::istream& operator>>(std::istream& input, Value& value)
|
|
||||||
{
|
|
||||||
std::string inputString;
|
|
||||||
|
|
||||||
char buffer[4096];
|
|
||||||
while (input.read(buffer, sizeof(buffer))) {
|
|
||||||
inputString.append(buffer, sizeof(buffer));
|
|
||||||
}
|
|
||||||
inputString.append(buffer, input.gcount());
|
|
||||||
|
|
||||||
value = Job(inputString).fire();
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& output, const Value& value)
|
|
||||||
{
|
|
||||||
return output << value.dump(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void format(Util::Format::Builder& builder, const Value& value)
|
|
||||||
{
|
|
||||||
builder.putString(value.dump(4));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // nullptr_t, size_t
|
|
||||||
#include <cstdint> // uint8_t, uint32_t
|
|
||||||
#include <initializer_list>
|
|
||||||
#include <iostream> // istream, ostream
|
|
||||||
#include <string>
|
|
||||||
#include <utility> // forward
|
|
||||||
|
|
||||||
#include "util/format/builder.h"
|
|
||||||
#include "util/json/fromjson.h"
|
|
||||||
#include "util/json/tojson.h"
|
|
||||||
|
|
||||||
namespace Util::JSON {
|
|
||||||
|
|
||||||
class Array;
|
|
||||||
class Object;
|
|
||||||
|
|
||||||
class Value {
|
|
||||||
private:
|
|
||||||
friend Detail::jsonConstructor;
|
|
||||||
friend class Parser;
|
|
||||||
friend class Serializer;
|
|
||||||
|
|
||||||
public:
|
|
||||||
enum class Type : uint8_t {
|
|
||||||
Null, // null (case sensitive!)
|
|
||||||
Bool, // true/false (case sensitive!)
|
|
||||||
Number, // 123
|
|
||||||
String, // ""
|
|
||||||
Array, // []
|
|
||||||
Object, // {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// --------------------------------------
|
|
||||||
|
|
||||||
// Constructors
|
|
||||||
Value(std::nullptr_t = nullptr);
|
|
||||||
Value(Type type);
|
|
||||||
Value(const std::initializer_list<Value>& values);
|
|
||||||
template<typename T>
|
|
||||||
Value(T value)
|
|
||||||
{
|
|
||||||
toJson(*this, std::forward<T>(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rule of Five:
|
|
||||||
// Copy constructor
|
|
||||||
Value(const Value& other);
|
|
||||||
// Move constructor
|
|
||||||
Value(Value&& other) noexcept;
|
|
||||||
// Copy assignment
|
|
||||||
// Move assignment
|
|
||||||
Value& operator=(Value other);
|
|
||||||
// Destructor
|
|
||||||
virtual ~Value() { destroy(); }
|
|
||||||
|
|
||||||
friend void swap(Value& left, Value& right) noexcept;
|
|
||||||
|
|
||||||
// --------------------------------------
|
|
||||||
|
|
||||||
static Value parse(std::string_view input);
|
|
||||||
static Value parse(std::ifstream& file);
|
|
||||||
std::string dump(const uint32_t indent = 0, const char indentCharacter = ' ') const;
|
|
||||||
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
void emplace_back(Value value);
|
|
||||||
void emplace(const std::string& key, Value value);
|
|
||||||
|
|
||||||
bool exists(size_t index) const;
|
|
||||||
bool exists(const std::string& key) const;
|
|
||||||
|
|
||||||
// --------------------------------------
|
|
||||||
|
|
||||||
// Array index operator
|
|
||||||
Value& operator[](size_t index);
|
|
||||||
Value& operator[](const std::string& key);
|
|
||||||
const Value& operator[](size_t index) const;
|
|
||||||
const Value& operator[](const std::string& key) const;
|
|
||||||
|
|
||||||
Value& at(size_t index);
|
|
||||||
Value& at(const std::string& key);
|
|
||||||
const Value& at(size_t index) const;
|
|
||||||
const Value& at(const std::string& key) const;
|
|
||||||
|
|
||||||
// --------------------------------------
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
T get() const
|
|
||||||
{
|
|
||||||
T type;
|
|
||||||
fromJson(*this, type);
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
void getTo(T& type) const
|
|
||||||
{
|
|
||||||
fromJson(*this, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------
|
|
||||||
|
|
||||||
Type type() const { return m_type; }
|
|
||||||
size_t size() const;
|
|
||||||
|
|
||||||
bool asBool() const { return m_value.boolean; }
|
|
||||||
double asDouble() const { return m_value.number; }
|
|
||||||
const std::string& asString() const { return *m_value.string; }
|
|
||||||
const Array& asArray() const { return *m_value.array; }
|
|
||||||
const Object& asObject() const { return *m_value.object; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
void destroy();
|
|
||||||
|
|
||||||
Type m_type { Type::Null };
|
|
||||||
|
|
||||||
union {
|
|
||||||
bool boolean;
|
|
||||||
double number;
|
|
||||||
std::string* string;
|
|
||||||
Array* array;
|
|
||||||
Object* object;
|
|
||||||
} m_value {};
|
|
||||||
};
|
|
||||||
|
|
||||||
std::istream& operator>>(std::istream& input, Value& value);
|
|
||||||
std::ostream& operator<<(std::ostream& output, const Value& value);
|
|
||||||
|
|
||||||
void format(Util::Format::Builder& builder, const Value& value);
|
|
||||||
|
|
||||||
} // namespace Util::JSON
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User-defined string literal
|
|
||||||
*
|
|
||||||
* Example usage: auto json = "[ 3.14, true, null ]"_json;
|
|
||||||
*/
|
|
||||||
inline Util::JSON::Value operator"" _json(const char* input, size_t length)
|
|
||||||
{
|
|
||||||
return Util::JSON::Value::parse(std::string(input, length));
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // uint32_t
|
|
||||||
#include <cstring> // strlen
|
|
||||||
#include <fstream> // ifstream
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/format/format.h"
|
|
||||||
#include "util/meta/compiler.h"
|
|
||||||
|
|
||||||
#define CRASH() asm volatile("int $0x03");
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
#define VERIFY(expr, ...) (static_cast<bool>(expr) ? (void)0 : Util::__assertion_failed(#expr, __FILE__, __LINE__, FUNCTION_MACRO __VA_OPT__(, ) __VA_ARGS__))
|
|
||||||
#define VERIFY_NOT_REACHED() VERIFY(false)
|
|
||||||
#else
|
|
||||||
#define VERIFY(expr, ...) (static_cast<bool>(expr) ? (void)0 : CRASH()
|
|
||||||
#define VERIFY_NOT_REACHED() CRASH()
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef NDEBUG
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
template<typename... Parameters>
|
|
||||||
inline void __assertion_failed(const char* assertion, const char* file, uint32_t line, const char* function, const Parameters&... parameters)
|
|
||||||
{
|
|
||||||
// Get the line that caused the error
|
|
||||||
std::ifstream source(file);
|
|
||||||
std::string content;
|
|
||||||
if (source.is_open()) {
|
|
||||||
for (uint32_t i = 0; std::getline(source, content); ++i) {
|
|
||||||
if (i == line - 1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Replace tab indentation with spaces
|
|
||||||
size_t tabs = content.find_first_not_of('\t');
|
|
||||||
if (tabs > 0 && tabs < content.size()) {
|
|
||||||
content = std::string(tabs * 4, ' ') + content.substr(tabs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the assertion in the line
|
|
||||||
size_t column = content.find(assertion);
|
|
||||||
size_t assertionLength = strlen(assertion);
|
|
||||||
if (column == std::string::npos) {
|
|
||||||
column = content.find_first_not_of(' ');
|
|
||||||
assertionLength = content.length() - column;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error message
|
|
||||||
fprintf(stderr,
|
|
||||||
"\033[;1m%s:%u:%zu "
|
|
||||||
"\033[31;1merror: "
|
|
||||||
"\033[0massertion failed",
|
|
||||||
file, line, column + 1);
|
|
||||||
if constexpr (sizeof...(Parameters) > 0) {
|
|
||||||
fprintf(stderr, ": ");
|
|
||||||
// Cant use the formatting library to print asserts caused by the formatting library
|
|
||||||
std::string_view functionString = function;
|
|
||||||
if (functionString.find("Util::Format::") != std::string_view::npos
|
|
||||||
&& functionString.find("Util::GenericLexer::") != std::string_view::npos) {
|
|
||||||
std::string message;
|
|
||||||
formatTo(message, parameters...);
|
|
||||||
fprintf(stderr, "%s", message.c_str());
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
fprintf(stderr, parameters...);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code line
|
|
||||||
fprintf(stderr, "\n %u | %s\033[31;1m%s\033[0m%s\n", line,
|
|
||||||
content.substr(0, column).c_str(), // Whitespace at front
|
|
||||||
content.substr(column, assertionLength).c_str(), // Error portion
|
|
||||||
content.substr(column + assertionLength).c_str()); // Rest of the line
|
|
||||||
|
|
||||||
// Arrow pointer
|
|
||||||
fprintf(stderr, " %s | %s\033[31;1m^%s\033[0m\n",
|
|
||||||
std::string(std::to_string(line).length(), ' ').c_str(), // Line number spacing
|
|
||||||
std::string(column, ' ').c_str(), // Content spacing
|
|
||||||
std::string(assertionLength - 1, '~').c_str()); // Arrow pointer
|
|
||||||
|
|
||||||
CRASH();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
#endif
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// Compiler
|
|
||||||
#if defined(__clang__)
|
|
||||||
#define GCC
|
|
||||||
#elif defined(__GNUG__) || (defined(__GNUC__) && defined(__cplusplus))
|
|
||||||
#define GCC
|
|
||||||
#elif defined(__INTEL_COMPILER) // Supports some GCC extensions
|
|
||||||
#define GCC
|
|
||||||
#elif defined(_MSC_VER)
|
|
||||||
#define MSVC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Non-standard function macro
|
|
||||||
#ifdef GCC
|
|
||||||
#define FUNCTION_MACRO __PRETTY_FUNCTION__ // GCC extension
|
|
||||||
#elif MSVC
|
|
||||||
#define FUNCTION_MACRO __FUNCSIG__
|
|
||||||
#else
|
|
||||||
#define FUNCTION_MACRO __func__ // C99
|
|
||||||
#endif
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <type_traits> // is_integral_v, floating_point_v
|
|
||||||
|
|
||||||
namespace Util::Concepts {
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
concept Integral = std::is_integral_v<T>;
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
concept FloatingPoint = std::is_floating_point_v<T>;
|
|
||||||
|
|
||||||
} // namespace Util::Concepts
|
|
||||||
|
|
||||||
using Util::Concepts::FloatingPoint;
|
|
||||||
using Util::Concepts::Integral;
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
namespace Detail {
|
|
||||||
|
|
||||||
// Avoid ODR (One Definition Rule) violations,
|
|
||||||
// variable templates are required to have external linkage
|
|
||||||
template<typename T>
|
|
||||||
constexpr T staticConst {};
|
|
||||||
|
|
||||||
} // namespace Detail
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#include <cstdio> // pclose, perror, popen
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include "util/shell.h"
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
Shell::Shell()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell::Shell(const std::string& output, int status)
|
|
||||||
: m_output(output)
|
|
||||||
, m_status(status)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell Shell::operator()(const char* command)
|
|
||||||
{
|
|
||||||
FILE* shell = popen(command, "r");
|
|
||||||
if (!shell) {
|
|
||||||
perror("\033[31;1mError:\033[0m popen");
|
|
||||||
return { "", -1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string output;
|
|
||||||
|
|
||||||
constexpr int bufferSize = 4096;
|
|
||||||
char buffer[bufferSize];
|
|
||||||
while (fgets(buffer, sizeof(buffer), shell)) {
|
|
||||||
output.append(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
int status = pclose(shell);
|
|
||||||
if (status < 0) {
|
|
||||||
perror("\033[31;1mError:\033[0m pclose");
|
|
||||||
}
|
|
||||||
|
|
||||||
return { output, status };
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell Shell::operator()(std::string command)
|
|
||||||
{
|
|
||||||
return operator()(command.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell Shell::operator()(std::string_view command)
|
|
||||||
{
|
|
||||||
return operator()(command.data());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,328 +0,0 @@
|
|||||||
#include <cerrno> // errno, EAGAIN, EINTR
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdio> // perror, ssize_t
|
|
||||||
#include <cstdlib> // exit, WEXITSTATUS
|
|
||||||
#include <cstring> // strcpy, strtok
|
|
||||||
#include <functional> // function
|
|
||||||
#include <sstream> // istringstream
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
#include <sys/wait.h> // waitpid
|
|
||||||
#include <unistd.h> // close, dup2, execvp, fork, pipe, read
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "util/system.h"
|
|
||||||
|
|
||||||
namespace Util {
|
|
||||||
|
|
||||||
System::System()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
System::System(const std::vector<std::string>& arguments)
|
|
||||||
: m_arguments(arguments)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()()
|
|
||||||
{
|
|
||||||
return exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(const char* command)
|
|
||||||
{
|
|
||||||
return operator()(std::string { command });
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(std::string command)
|
|
||||||
{
|
|
||||||
std::vector<std::string> arguments;
|
|
||||||
|
|
||||||
size_t index = 0;
|
|
||||||
while (index != std::string::npos) {
|
|
||||||
index = command.find_first_of(" ");
|
|
||||||
arguments.push_back(command.substr(0, index));
|
|
||||||
command = command.substr(index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { arguments };
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(std::string_view command)
|
|
||||||
{
|
|
||||||
return operator()(std::string { command });
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(const std::vector<const char*>& arguments)
|
|
||||||
{
|
|
||||||
std::vector<std::string> stringArguments(arguments.size(), "");
|
|
||||||
for (size_t i = 0; i < arguments.size(); ++i) {
|
|
||||||
stringArguments[i] = arguments[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return { stringArguments };
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(const std::vector<std::string>& arguments)
|
|
||||||
{
|
|
||||||
return { arguments };
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator()(const std::vector<std::string_view>& arguments)
|
|
||||||
{
|
|
||||||
std::vector<std::string> stringArguments(arguments.size(), "");
|
|
||||||
for (size_t i = 0; i < arguments.size(); ++i) {
|
|
||||||
stringArguments[i] = arguments[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
return { stringArguments };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shell equivalent ;
|
|
||||||
System System::operator+(System rhs)
|
|
||||||
{
|
|
||||||
auto lhs = *this;
|
|
||||||
|
|
||||||
lhs.exec();
|
|
||||||
rhs.m_output.append(lhs.m_output);
|
|
||||||
rhs.m_error.append(lhs.m_error);
|
|
||||||
rhs.exec();
|
|
||||||
|
|
||||||
return rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator|(System rhs)
|
|
||||||
{
|
|
||||||
auto lhs = *this;
|
|
||||||
|
|
||||||
lhs.exec();
|
|
||||||
rhs.exec(lhs.m_output);
|
|
||||||
|
|
||||||
return rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator&&(System rhs)
|
|
||||||
{
|
|
||||||
auto lhs = *this;
|
|
||||||
|
|
||||||
lhs.exec();
|
|
||||||
if (lhs.m_status > 0) {
|
|
||||||
return lhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
rhs.m_output.append(lhs.m_output);
|
|
||||||
rhs.m_error.append(lhs.m_error);
|
|
||||||
rhs.exec();
|
|
||||||
|
|
||||||
return rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
System System::operator||(System rhs)
|
|
||||||
{
|
|
||||||
auto lhs = *this;
|
|
||||||
|
|
||||||
lhs.exec();
|
|
||||||
if (lhs.m_status == 0) {
|
|
||||||
return lhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
rhs.m_output.append(lhs.m_output);
|
|
||||||
rhs.m_error.append(lhs.m_error);
|
|
||||||
rhs.exec();
|
|
||||||
|
|
||||||
return rhs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cut -f -d
|
|
||||||
System& System::cut(uint32_t field, char delimiter)
|
|
||||||
{
|
|
||||||
exec();
|
|
||||||
|
|
||||||
return apply([&field, &delimiter](std::vector<std::string>& lines) {
|
|
||||||
for (auto& line : lines) {
|
|
||||||
size_t count = 1;
|
|
||||||
size_t index = 0;
|
|
||||||
while (index != std::string::npos) {
|
|
||||||
if (count == field) {
|
|
||||||
line = line.substr(0, line.find_first_of(delimiter));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
index = line.find_first_of(delimiter);
|
|
||||||
line = line.substr(index + 1);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
System& System::sort(bool unique)
|
|
||||||
{
|
|
||||||
exec();
|
|
||||||
|
|
||||||
return apply([&unique](std::vector<std::string>& lines) {
|
|
||||||
std::sort(lines.begin(), lines.end());
|
|
||||||
|
|
||||||
if (unique) {
|
|
||||||
auto last = std::unique(lines.begin(), lines.end());
|
|
||||||
lines.erase(last, lines.end());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// tail -n
|
|
||||||
System& System::tail(int32_t number, bool starting)
|
|
||||||
{
|
|
||||||
exec();
|
|
||||||
|
|
||||||
return apply([&number, &starting](std::vector<std::string>& lines) {
|
|
||||||
number = abs(number);
|
|
||||||
if (!starting) {
|
|
||||||
lines.erase(lines.begin(), lines.end() - number);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
lines.erase(lines.begin(), lines.begin() + number - 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
System& System::apply(LineCallback callback)
|
|
||||||
{
|
|
||||||
exec();
|
|
||||||
|
|
||||||
std::vector<std::string> lines;
|
|
||||||
|
|
||||||
auto stream = std::istringstream(m_output);
|
|
||||||
std::string line;
|
|
||||||
while (std::getline(stream, line)) {
|
|
||||||
lines.push_back(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(lines);
|
|
||||||
|
|
||||||
m_output.clear();
|
|
||||||
for (size_t i = 0; i < lines.size(); ++i) {
|
|
||||||
m_output.append(lines.at(i) + '\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void System::print(const std::vector<std::string>& arguments)
|
|
||||||
{
|
|
||||||
if (!arguments.size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("----------\n");
|
|
||||||
printf("size: %zu\n", arguments.size());
|
|
||||||
printf("command: ");
|
|
||||||
for (size_t i = 0; i < arguments.size(); ++i) {
|
|
||||||
printf("%s ", arguments.at(i).c_str());
|
|
||||||
}
|
|
||||||
printf("\n");
|
|
||||||
printf("----------\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
System System::exec(std::string input)
|
|
||||||
{
|
|
||||||
if (m_arguments.empty()) {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
int stdinFd[2];
|
|
||||||
int stdoutFd[2];
|
|
||||||
int stderrFd[2];
|
|
||||||
if (pipe(stdinFd) < 0) {
|
|
||||||
perror("\033[31;1mError:\033[0m pipe");
|
|
||||||
}
|
|
||||||
if (pipe(stdoutFd) < 0) {
|
|
||||||
perror("\033[31;1mError:\033[0m pipe");
|
|
||||||
}
|
|
||||||
if (pipe(stderrFd) < 0) {
|
|
||||||
perror("\033[31;1mError:\033[0m pipe");
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t pid = fork();
|
|
||||||
switch (pid) {
|
|
||||||
// Failed
|
|
||||||
case -1:
|
|
||||||
perror("\033[31;1mError:\033[0m fork");
|
|
||||||
break;
|
|
||||||
// Child
|
|
||||||
case 0: {
|
|
||||||
close(stdinFd[WriteFileDescriptor]);
|
|
||||||
dup2(stdinFd[ReadFileDescriptor], fileno(stdin));
|
|
||||||
close(stdinFd[ReadFileDescriptor]);
|
|
||||||
|
|
||||||
close(stdoutFd[ReadFileDescriptor]);
|
|
||||||
dup2(stdoutFd[WriteFileDescriptor], fileno(stdout));
|
|
||||||
close(stdoutFd[WriteFileDescriptor]);
|
|
||||||
|
|
||||||
close(stderrFd[ReadFileDescriptor]);
|
|
||||||
dup2(stderrFd[WriteFileDescriptor], fileno(stderr));
|
|
||||||
close(stderrFd[WriteFileDescriptor]);
|
|
||||||
|
|
||||||
std::vector<char*> charArguments(m_arguments.size() + 1, 0);
|
|
||||||
for (size_t i = 0; i < m_arguments.size(); ++i) {
|
|
||||||
charArguments[i] = const_cast<char*>(m_arguments[i].c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
execvp(charArguments[0], &charArguments[0]);
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
// Parent
|
|
||||||
default:
|
|
||||||
m_arguments.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
close(stdinFd[ReadFileDescriptor]);
|
|
||||||
if (!input.empty()) {
|
|
||||||
write(stdinFd[WriteFileDescriptor], input.c_str(), input.size());
|
|
||||||
}
|
|
||||||
close(stdinFd[WriteFileDescriptor]);
|
|
||||||
|
|
||||||
readFromFileDescriptor(stdoutFd, m_output);
|
|
||||||
readFromFileDescriptor(stderrFd, m_error);
|
|
||||||
|
|
||||||
int result;
|
|
||||||
do {
|
|
||||||
result = waitpid(pid, &m_status, 0);
|
|
||||||
} while (result == -1 && errno == EINTR);
|
|
||||||
m_status = WEXITSTATUS(m_status);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void System::readFromFileDescriptor(int fileDescriptor[2], std::string& output)
|
|
||||||
{
|
|
||||||
close(fileDescriptor[WriteFileDescriptor]);
|
|
||||||
|
|
||||||
constexpr int bufferSize = 4096;
|
|
||||||
char buffer[bufferSize];
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
const ssize_t result = read(fileDescriptor[ReadFileDescriptor], buffer, bufferSize);
|
|
||||||
if (result > 0) {
|
|
||||||
output.append(buffer, result);
|
|
||||||
}
|
|
||||||
// EOF
|
|
||||||
if (result == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Error
|
|
||||||
else if (result == -1) {
|
|
||||||
if (errno != EAGAIN && errno != EINTR) {
|
|
||||||
perror("\033[31;1mError:\033[0m read");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(fileDescriptor[ReadFileDescriptor]);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,131 +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_running(true)
|
|
||||||
, m_accumulated(TimePoint::min())
|
|
||||||
, m_start(now())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer::Timer(const TimePoint& timePoint)
|
|
||||||
: m_running(true)
|
|
||||||
, m_accumulated(TimePoint::min())
|
|
||||||
, m_start(timePoint)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
Timer Timer::operator-(const Timer& timer)
|
|
||||||
{
|
|
||||||
return Timer(TimePoint { m_start - timer.start() });
|
|
||||||
}
|
|
||||||
|
|
||||||
TimePoint Timer::now()
|
|
||||||
{
|
|
||||||
return std::chrono::high_resolution_clock::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Timer::pause()
|
|
||||||
{
|
|
||||||
if (!m_running) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_accumulated += now() - m_start;
|
|
||||||
m_running = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Timer::resume()
|
|
||||||
{
|
|
||||||
if (m_running) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_running = true;
|
|
||||||
m_start = now();
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
uint64_t elapsed = 0;
|
|
||||||
|
|
||||||
if (m_running) {
|
|
||||||
elapsed += std::chrono::duration_cast<T>(now() - m_start).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
elapsed += std::chrono::duration_cast<T>(m_accumulated - TimePoint::min()).count();
|
|
||||||
|
|
||||||
return elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::elapsedSeconds()
|
|
||||||
{
|
|
||||||
return elapsed<std::chrono::seconds>();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::elapsedMilliseconds()
|
|
||||||
{
|
|
||||||
return elapsed<std::chrono::milliseconds>();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::elapsedMicroseconds()
|
|
||||||
{
|
|
||||||
return elapsed<std::chrono::microseconds>();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t Timer::elapsedNanoseconds()
|
|
||||||
{
|
|
||||||
return elapsed<std::chrono::nanoseconds>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Timer::fancyPrint(uint64_t nanoseconds)
|
|
||||||
{
|
|
||||||
if (nanoseconds > 999999999) {
|
|
||||||
printf("%.3fs", nanoseconds / 1000000000.0);
|
|
||||||
}
|
|
||||||
else if (nanoseconds > 999999) {
|
|
||||||
printf("%.0fms", nanoseconds / 1000000.0);
|
|
||||||
}
|
|
||||||
else if (nanoseconds > 999) {
|
|
||||||
printf("%.0fμs", nanoseconds / 1000.0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
printf("%luns", nanoseconds);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#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);
|
|
||||||
|
|
||||||
Timer operator-(const Timer& timer);
|
|
||||||
|
|
||||||
static TimePoint now();
|
|
||||||
void pause();
|
|
||||||
void resume();
|
|
||||||
|
|
||||||
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:
|
|
||||||
bool m_running { true };
|
|
||||||
TimePoint m_accumulated;
|
|
||||||
TimePoint m_start;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Util
|
|
||||||
@@ -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
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#include "testsuite.h"
|
|
||||||
|
|
||||||
int main(int, const char*[])
|
|
||||||
{
|
|
||||||
Test::TestSuite::the().run();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#ifndef TEST_CASE_H
|
|
||||||
#define TEST_CASE_H
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#define __TEST_CASE_FUNCTION(x) __test##x
|
|
||||||
#define __TEST_CASE_STRUCT(x) __testStruct##x
|
|
||||||
|
|
||||||
#define TEST_CASE(x) \
|
|
||||||
static void __TEST_CASE_FUNCTION(x)(); \
|
|
||||||
struct __TEST_CASE_STRUCT(x) { \
|
|
||||||
__TEST_CASE_STRUCT(x) \
|
|
||||||
() \
|
|
||||||
{ \
|
|
||||||
Test::TestSuite::the().addCase( \
|
|
||||||
{ #x, __TEST_CASE_FUNCTION(x) }); \
|
|
||||||
} \
|
|
||||||
}; \
|
|
||||||
static struct __TEST_CASE_STRUCT(x) __TEST_CASE_STRUCT(x); \
|
|
||||||
static void __TEST_CASE_FUNCTION(x)()
|
|
||||||
|
|
||||||
namespace Test {
|
|
||||||
|
|
||||||
using TestFunction = std::function<void()>;
|
|
||||||
|
|
||||||
class TestCase {
|
|
||||||
public:
|
|
||||||
TestCase(const char* name, TestFunction&& function)
|
|
||||||
: m_name(name)
|
|
||||||
, m_function(function)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* name() const { return m_name; }
|
|
||||||
const TestFunction& function() const { return m_function; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
const char* m_name { nullptr };
|
|
||||||
TestFunction m_function;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Test
|
|
||||||
|
|
||||||
#endif // TEST_CASE_H
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdio> // fclose, fopen, printf, stdout
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/timer.h"
|
|
||||||
|
|
||||||
namespace Test {
|
|
||||||
|
|
||||||
TestSuite::TestSuite(s)
|
|
||||||
{
|
|
||||||
m_outputStd = stdout;
|
|
||||||
m_outputErr = stderr;
|
|
||||||
m_outputNull = fopen("/dev/null", "w"); // Windows: nul
|
|
||||||
}
|
|
||||||
|
|
||||||
TestSuite::~TestSuite()
|
|
||||||
{
|
|
||||||
fclose(m_outputNull);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TestSuite::run()
|
|
||||||
{
|
|
||||||
const char* escapePass = "\033[42;30;1m";
|
|
||||||
const char* escapeFail = "\033[41;1m";
|
|
||||||
const char* escapeGreen = "\033[32m";
|
|
||||||
const char* escapeGrey = "\033[37m";
|
|
||||||
const char* escapeRed = "\033[31m";
|
|
||||||
const char* escapeReset = "\033[0m";
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
printf("---- Running %zu Test Cases ----\n", m_cases.size());
|
|
||||||
|
|
||||||
Util::Timer totalTimer;
|
|
||||||
|
|
||||||
size_t caseFailedCount = 0;
|
|
||||||
for (size_t i = 0; i < m_cases.size(); ++i) {
|
|
||||||
|
|
||||||
printf(" START %s (%zu/%zu)\n", m_cases.at(i).name(), i + 1, m_cases.size());
|
|
||||||
m_currentTestCasePassed = true;
|
|
||||||
|
|
||||||
Util::Timer caseTimer;
|
|
||||||
m_cases.at(i).function()();
|
|
||||||
double elapsed = caseTimer.elapsedNanoseconds();
|
|
||||||
|
|
||||||
std::string state;
|
|
||||||
if (m_currentTestCasePassed) {
|
|
||||||
state.append(escapePass);
|
|
||||||
state.append(" PASS ");
|
|
||||||
state.append(escapeReset);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
caseFailedCount++;
|
|
||||||
state.append(escapeFail);
|
|
||||||
state.append(" FAIL ");
|
|
||||||
state.append(escapeReset);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("%s %s %s(", state.c_str(), m_cases.at(i).name(), escapeGrey);
|
|
||||||
Util::Timer::fancyPrint(elapsed);
|
|
||||||
printf(")%s\n", escapeReset);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
printf("Tests: %s%zu failed%s, %s%zu passed%s, %zu total\n",
|
|
||||||
escapeRed, caseFailedCount, escapeReset,
|
|
||||||
escapeGreen, m_cases.size() - caseFailedCount, escapeReset,
|
|
||||||
m_cases.size());
|
|
||||||
|
|
||||||
printf("Time: ");
|
|
||||||
Util::Timer::fancyPrint(totalTimer.elapsedNanoseconds());
|
|
||||||
printf("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Test
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#ifndef TEST_SUITE_H
|
|
||||||
#define TEST_SUITE_H
|
|
||||||
|
|
||||||
#include <cstdio> // FILE
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "util/singleton.h"
|
|
||||||
|
|
||||||
namespace Test {
|
|
||||||
|
|
||||||
class TestSuite final : public Util::Singleton<TestSuite> {
|
|
||||||
public:
|
|
||||||
TestSuite(s);
|
|
||||||
virtual ~TestSuite();
|
|
||||||
|
|
||||||
void run();
|
|
||||||
void addCase(const TestCase& testCase) { m_cases.push_back(testCase); }
|
|
||||||
void currentTestCaseFailed() { m_currentTestCasePassed = false; }
|
|
||||||
|
|
||||||
FILE* outputStd() const { return m_outputStd; }
|
|
||||||
FILE* outputErr() const { return m_outputErr; }
|
|
||||||
FILE* outputNull() const { return m_outputNull; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool m_currentTestCasePassed { true };
|
|
||||||
FILE* m_outputStd { nullptr };
|
|
||||||
FILE* m_outputErr { nullptr };
|
|
||||||
FILE* m_outputNull { nullptr };
|
|
||||||
|
|
||||||
std::vector<TestCase> m_cases;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Test
|
|
||||||
|
|
||||||
#endif // TEST_SUITE_H
|
|
||||||
+95
-23
@@ -1,11 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Riyyi
|
* Copyright (C) 2022,2025 Riyyi
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: MIT
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
#include <cstddef> // size_t
|
||||||
#include <cstdint> // uint32_t
|
|
||||||
#include <cstdio> // stderr
|
#include <cstdio> // stderr
|
||||||
#include <filesystem> // path
|
#include <filesystem> // path
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -13,14 +12,14 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "ruc/file.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "dotfile.h"
|
#include "dotfile.h"
|
||||||
#include "machine.h"
|
#include "machine.h"
|
||||||
#include "macro.h"
|
#include "macro.h"
|
||||||
#include "testcase.h"
|
#include "testcase.h"
|
||||||
#include "testsuite.h"
|
#include "testsuite.h"
|
||||||
#include "util/file.h"
|
|
||||||
#include "util/system.h"
|
|
||||||
|
|
||||||
const bool root = !geteuid() ? true : false;
|
const bool root = !geteuid() ? true : false;
|
||||||
const std::filesystem::path homeDirectory = "/home/" + Machine::the().username();
|
const std::filesystem::path homeDirectory = "/home/" + Machine::the().username();
|
||||||
@@ -28,7 +27,7 @@ const size_t homeDirectorySize = homeDirectory.string().size();
|
|||||||
|
|
||||||
void createTestDotfiles(const std::vector<std::string>& fileNames, const std::vector<std::string>& fileContents, bool asRoot = false)
|
void createTestDotfiles(const std::vector<std::string>& fileNames, const std::vector<std::string>& fileContents, bool asRoot = false)
|
||||||
{
|
{
|
||||||
EXPECT(fileNames.size() == fileContents.size(), return );
|
EXPECT(fileNames.size() == fileContents.size(), return);
|
||||||
|
|
||||||
if (root && !asRoot) {
|
if (root && !asRoot) {
|
||||||
setegid(Machine::the().gid());
|
setegid(Machine::the().gid());
|
||||||
@@ -43,7 +42,7 @@ void createTestDotfiles(const std::vector<std::string>& fileNames, const std::ve
|
|||||||
std::filesystem::create_directories(directory);
|
std::filesystem::create_directories(directory);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto file = Util::File::create(fileName);
|
auto file = ruc::File::create(fileName);
|
||||||
if (file.data().size() == 0) {
|
if (file.data().size() == 0) {
|
||||||
file.append(fileContents.at(i).c_str()).flush();
|
file.append(fileContents.at(i).c_str()).flush();
|
||||||
}
|
}
|
||||||
@@ -374,8 +373,8 @@ TEST_CASE(AddDotfiles)
|
|||||||
EXPECT(std::filesystem::exists(file), continue);
|
EXPECT(std::filesystem::exists(file), continue);
|
||||||
EXPECT(std::filesystem::exists(file.substr(homeDirectorySize + 1)), continue);
|
EXPECT(std::filesystem::exists(file.substr(homeDirectorySize + 1)), continue);
|
||||||
|
|
||||||
Util::File lhs(file);
|
ruc::File lhs(file);
|
||||||
Util::File rhs(file.substr(homeDirectorySize + 1));
|
ruc::File rhs(file.substr(homeDirectorySize + 1));
|
||||||
EXPECT_EQ(lhs.data(), rhs.data());
|
EXPECT_EQ(lhs.data(), rhs.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,9 +383,9 @@ TEST_CASE(AddDotfiles)
|
|||||||
|
|
||||||
TEST_CASE(AddNonExistentDotfiles)
|
TEST_CASE(AddNonExistentDotfiles)
|
||||||
{
|
{
|
||||||
stderr = Test::TestSuite::the().outputNull();
|
stderr = test::TestSuite::the().outputNull();
|
||||||
Dotfile::the().add({ homeDirectory / "__non-existent-test-file" });
|
Dotfile::the().add({ homeDirectory / "__non-existent-test-file" });
|
||||||
stderr = Test::TestSuite::the().outputErr();
|
stderr = test::TestSuite::the().outputErr();
|
||||||
|
|
||||||
EXPECT(!std::filesystem::exists("__non-existent-test-file"));
|
EXPECT(!std::filesystem::exists("__non-existent-test-file"));
|
||||||
removeTestDotfiles({ "__non-existent-test-file" });
|
removeTestDotfiles({ "__non-existent-test-file" });
|
||||||
@@ -430,8 +429,8 @@ TEST_CASE(PullDotfiles)
|
|||||||
EXPECT(std::filesystem::exists(homeFileNames.at(i)), continue);
|
EXPECT(std::filesystem::exists(homeFileNames.at(i)), continue);
|
||||||
EXPECT(std::filesystem::exists(fileNames.at(i)), continue);
|
EXPECT(std::filesystem::exists(fileNames.at(i)), continue);
|
||||||
|
|
||||||
Util::File lhs(homeFileNames.at(i));
|
ruc::File lhs(homeFileNames.at(i));
|
||||||
Util::File rhs(fileNames.at(i));
|
ruc::File rhs(fileNames.at(i));
|
||||||
EXPECT_EQ(lhs.data(), rhs.data());
|
EXPECT_EQ(lhs.data(), rhs.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,8 +462,8 @@ TEST_CASE(PushDotfiles)
|
|||||||
EXPECT(std::filesystem::exists(file), continue);
|
EXPECT(std::filesystem::exists(file), continue);
|
||||||
EXPECT(std::filesystem::exists(homeDirectory / file), continue);
|
EXPECT(std::filesystem::exists(homeDirectory / file), continue);
|
||||||
|
|
||||||
Util::File lhs(file);
|
ruc::File lhs(file);
|
||||||
Util::File rhs(homeDirectory / file);
|
ruc::File rhs((homeDirectory / file).string());
|
||||||
EXPECT_EQ(lhs.data(), rhs.data());
|
EXPECT_EQ(lhs.data(), rhs.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,7 +502,7 @@ TEST_CASE(PushDotfilesWithIgnorePattern)
|
|||||||
TEST_CASE(PushDotfilesSelectivelyComment)
|
TEST_CASE(PushDotfilesSelectivelyComment)
|
||||||
{
|
{
|
||||||
std::vector<std::string> fileNames;
|
std::vector<std::string> fileNames;
|
||||||
for (size_t i = 0; i < 36; ++i) {
|
for (size_t i = 0; i < 44; ++i) {
|
||||||
fileNames.push_back("__test-file-" + std::to_string(i + 1));
|
fileNames.push_back("__test-file-" + std::to_string(i + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,6 +677,43 @@ test data /**/ comment
|
|||||||
/* test data /**/ uncomment */
|
/* test data /**/ uncomment */
|
||||||
/* <<< */
|
/* <<< */
|
||||||
)",
|
)",
|
||||||
|
|
||||||
|
// Comment <!---->
|
||||||
|
"<!-- >>> distro=" + placeholder + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> comment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + placeholder + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> comment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + placeholder + " -->" + R"(
|
||||||
|
test data <!----> comment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + placeholder + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> comment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
|
||||||
|
// Uncomment <!---->
|
||||||
|
"<!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> uncomment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro="
|
||||||
|
+ distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> uncomment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> uncomment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> uncomment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::string> pushedFileContents = {
|
std::vector<std::string> pushedFileContents = {
|
||||||
@@ -846,6 +882,42 @@ test data /**/ uncomment
|
|||||||
test data /**/ uncomment
|
test data /**/ uncomment
|
||||||
/* <<< */
|
/* <<< */
|
||||||
)",
|
)",
|
||||||
|
|
||||||
|
// Comment <!---->
|
||||||
|
"<!-- >>> distro=" + placeholder + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> comment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + placeholder + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> comment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + placeholder + " -->" + R"(
|
||||||
|
<!-- test data <!----> comment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + placeholder + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
<!-- test data <!----> comment -->
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
|
||||||
|
// Uncomment <!---->
|
||||||
|
"<!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> uncomment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> uncomment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> uncomment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
|
" <!-- >>> distro=" + distro + " hostname=" + hostname + " user=" + username + " -->" + R"(
|
||||||
|
test data <!----> uncomment
|
||||||
|
<!-- <<< -->
|
||||||
|
)",
|
||||||
};
|
};
|
||||||
|
|
||||||
createTestDotfiles(fileNames, fileContents);
|
createTestDotfiles(fileNames, fileContents);
|
||||||
@@ -857,7 +929,7 @@ test data /**/ uncomment
|
|||||||
EXPECT(std::filesystem::exists(file), continue);
|
EXPECT(std::filesystem::exists(file), continue);
|
||||||
EXPECT(std::filesystem::exists(homeDirectory / file), continue);
|
EXPECT(std::filesystem::exists(homeDirectory / file), continue);
|
||||||
|
|
||||||
Util::File lhs(homeDirectory / file);
|
ruc::File lhs((homeDirectory / file).string());
|
||||||
EXPECT_EQ(lhs.data(), pushedFileContents.at(i));
|
EXPECT_EQ(lhs.data(), pushedFileContents.at(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -866,7 +938,7 @@ test data /**/ uncomment
|
|||||||
|
|
||||||
TEST_CASE(AddSystemDotfiles)
|
TEST_CASE(AddSystemDotfiles)
|
||||||
{
|
{
|
||||||
EXPECT(geteuid() == 0, return );
|
EXPECT(geteuid() == 0, return);
|
||||||
|
|
||||||
Config::the().setSystemPatterns({ "/etc/", "/usr/lib/" });
|
Config::the().setSystemPatterns({ "/etc/", "/usr/lib/" });
|
||||||
Dotfile::the().add({ "/etc/group", "/usr/lib/os-release" });
|
Dotfile::the().add({ "/etc/group", "/usr/lib/os-release" });
|
||||||
@@ -881,7 +953,7 @@ TEST_CASE(AddSystemDotfiles)
|
|||||||
|
|
||||||
TEST_CASE(PullSystemDotfiles)
|
TEST_CASE(PullSystemDotfiles)
|
||||||
{
|
{
|
||||||
EXPECT(geteuid() == 0, return );
|
EXPECT(geteuid() == 0, return);
|
||||||
|
|
||||||
createTestDotfiles({ "etc/group" }, { "" }, true);
|
createTestDotfiles({ "etc/group" }, { "" }, true);
|
||||||
|
|
||||||
@@ -889,8 +961,8 @@ TEST_CASE(PullSystemDotfiles)
|
|||||||
Dotfile::the().pull({ "etc/group" });
|
Dotfile::the().pull({ "etc/group" });
|
||||||
Config::the().setSystemPatterns({});
|
Config::the().setSystemPatterns({});
|
||||||
|
|
||||||
Util::File lhs("/etc/group");
|
ruc::File lhs("/etc/group");
|
||||||
Util::File rhs(Config::the().workingDirectory() / "etc/group");
|
ruc::File rhs((Config::the().workingDirectory() / "etc/group").string());
|
||||||
EXPECT_EQ(lhs.data(), rhs.data());
|
EXPECT_EQ(lhs.data(), rhs.data());
|
||||||
|
|
||||||
std::filesystem::remove_all(Config::the().workingDirectory() / "etc");
|
std::filesystem::remove_all(Config::the().workingDirectory() / "etc");
|
||||||
@@ -909,7 +981,7 @@ TEST_CASE(AddSymlinkDotfiles)
|
|||||||
|
|
||||||
EXPECT(std::filesystem::is_symlink(symlinkFileName));
|
EXPECT(std::filesystem::is_symlink(symlinkFileName));
|
||||||
EXPECT_EQ(std::filesystem::read_symlink(symlinkFileName).string(), fileInHome);
|
EXPECT_EQ(std::filesystem::read_symlink(symlinkFileName).string(), fileInHome);
|
||||||
EXPECT_EQ(Util::File(symlinkFileName).data(), "the file contents");
|
EXPECT_EQ(ruc::File(symlinkFileName.string()).data(), "the file contents");
|
||||||
|
|
||||||
removeTestDotfiles({ symlinkInHome, fileInHome });
|
removeTestDotfiles({ symlinkInHome, fileInHome });
|
||||||
}
|
}
|
||||||
@@ -928,7 +1000,7 @@ TEST_CASE(PullSymlinkDotfiles)
|
|||||||
|
|
||||||
EXPECT(std::filesystem::is_symlink(symlinkFileName));
|
EXPECT(std::filesystem::is_symlink(symlinkFileName));
|
||||||
EXPECT_EQ(std::filesystem::read_symlink(symlinkFileName).string(), fileInHome);
|
EXPECT_EQ(std::filesystem::read_symlink(symlinkFileName).string(), fileInHome);
|
||||||
EXPECT_EQ(Util::File(symlinkFileName).data(), "the file contents");
|
EXPECT_EQ(ruc::File(symlinkFileName.string()).data(), "the file contents");
|
||||||
|
|
||||||
removeTestDotfiles({ symlinkInHome, fileInHome });
|
removeTestDotfiles({ symlinkInHome, fileInHome });
|
||||||
}
|
}
|
||||||
@@ -946,7 +1018,7 @@ TEST_CASE(PushSymlinkDotfiles)
|
|||||||
|
|
||||||
EXPECT(std::filesystem::is_symlink(symlinkInHome));
|
EXPECT(std::filesystem::is_symlink(symlinkInHome));
|
||||||
EXPECT_EQ(std::filesystem::read_symlink(symlinkInHome).string(), fileInHome);
|
EXPECT_EQ(std::filesystem::read_symlink(symlinkInHome).string(), fileInHome);
|
||||||
EXPECT_EQ(Util::File(symlinkInHome).data(), "the file contents");
|
EXPECT_EQ(ruc::File(symlinkInHome.string()).data(), "the file contents");
|
||||||
|
|
||||||
removeTestDotfiles({ symlinkInHome, fileInHome });
|
removeTestDotfiles({ symlinkInHome, fileInHome });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,949 +0,0 @@
|
|||||||
#include <functional> // function
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "macro.h"
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/argparser.h"
|
|
||||||
|
|
||||||
bool runParser(std::vector<const char*> arguments, std::function<void(Util::ArgParser&)> initializer = {})
|
|
||||||
{
|
|
||||||
stdout = Test::TestSuite::the().outputNull();
|
|
||||||
|
|
||||||
Util::ArgParser parser;
|
|
||||||
if (initializer) {
|
|
||||||
initializer(parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
arguments.insert(arguments.begin(), "app");
|
|
||||||
auto result = parser.parse(arguments.size(), arguments.data());
|
|
||||||
|
|
||||||
stdout = Test::TestSuite::the().outputStd();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(NoArguments)
|
|
||||||
{
|
|
||||||
auto result = runParser({});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(NonExistentArguments)
|
|
||||||
{
|
|
||||||
auto result = runParser({ "-n", "-e" });
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
|
|
||||||
result = runParser({ "--non", "--existent" });
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
|
|
||||||
result = runParser({ "-n", "-e", "--non", "--existent" });
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
|
|
||||||
result = runParser({ "no", "handling" });
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(RequiredStringArguments)
|
|
||||||
{
|
|
||||||
// Single required string argument
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "my-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string argument, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
|
|
||||||
// Multiple required string arguments
|
|
||||||
stringArg1 = "";
|
|
||||||
std::string stringArg2 = "";
|
|
||||||
std::string stringArg3 = "";
|
|
||||||
result = runParser({ "my", "required", "argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my");
|
|
||||||
EXPECT_EQ(stringArg2, "required");
|
|
||||||
EXPECT_EQ(stringArg3, "argument");
|
|
||||||
|
|
||||||
// Multiple required string arguments, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
stringArg2 = "";
|
|
||||||
stringArg3 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
EXPECT_EQ(stringArg2, "");
|
|
||||||
EXPECT_EQ(stringArg3, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(OptionalStringArguments)
|
|
||||||
{
|
|
||||||
// Single optional string argument
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "my-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string argument, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
|
|
||||||
// Multiple optional string arguments
|
|
||||||
stringArg1 = "";
|
|
||||||
std::string stringArg2 = "";
|
|
||||||
std::string stringArg3 = "";
|
|
||||||
result = runParser({ "my", "optional", "argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my");
|
|
||||||
EXPECT_EQ(stringArg2, "optional");
|
|
||||||
EXPECT_EQ(stringArg3, "argument");
|
|
||||||
|
|
||||||
// Multiple optional string arguments, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
stringArg2 = "";
|
|
||||||
stringArg3 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
EXPECT_EQ(stringArg2, "");
|
|
||||||
EXPECT_EQ(stringArg3, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(NonRequiredStringArguments)
|
|
||||||
{
|
|
||||||
// Single non-required string argument
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "my-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my-non-required-argument");
|
|
||||||
|
|
||||||
// Single non-required string argument, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
|
|
||||||
// Multiple non-required string arguments
|
|
||||||
stringArg1 = "";
|
|
||||||
std::string stringArg2 = "";
|
|
||||||
std::string stringArg3 = "";
|
|
||||||
result = runParser({ "my", "non-required", "argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "my");
|
|
||||||
EXPECT_EQ(stringArg2, "non-required");
|
|
||||||
EXPECT_EQ(stringArg3, "argument");
|
|
||||||
|
|
||||||
// Multiple non-required string arguments, not given
|
|
||||||
stringArg1 = "";
|
|
||||||
stringArg2 = "";
|
|
||||||
stringArg3 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg2, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg3, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringArg1, "");
|
|
||||||
EXPECT_EQ(stringArg2, "");
|
|
||||||
EXPECT_EQ(stringArg3, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(VectorStringArguments)
|
|
||||||
{
|
|
||||||
// Required vector string argument, not given
|
|
||||||
std::vector<std::string> vectorArg1 = {};
|
|
||||||
auto result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 0);
|
|
||||||
|
|
||||||
// Required vector string argument, one given
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "foo" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1[0], "foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string argument, two given
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "hello", "world" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 2);
|
|
||||||
if (vectorArg1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorArg1[0], "hello");
|
|
||||||
EXPECT_EQ(vectorArg1[1], "world");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(CombinationOfNonRequiredArguments)
|
|
||||||
{
|
|
||||||
// Optional arguments, one given
|
|
||||||
int intArg1 = 0;
|
|
||||||
double doubleArg1 = 0;
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "optional argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(intArg1, 0);
|
|
||||||
EXPECT_EQ(doubleArg1, 0);
|
|
||||||
EXPECT_EQ(stringArg1, "optional argument");
|
|
||||||
|
|
||||||
// Optional arguments, two given
|
|
||||||
intArg1 = 0;
|
|
||||||
doubleArg1 = 0;
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({ "999.999", "optional argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(intArg1, 0);
|
|
||||||
EXPECT_EQ(doubleArg1, 999.999);
|
|
||||||
EXPECT_EQ(stringArg1, "optional argument");
|
|
||||||
|
|
||||||
// Optional arguments, two given, one valid
|
|
||||||
intArg1 = 0;
|
|
||||||
doubleArg1 = 0;
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({ "999,999", "optional argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(intArg1, 0);
|
|
||||||
EXPECT_EQ(doubleArg1, 0);
|
|
||||||
EXPECT_EQ(stringArg1, "999,999");
|
|
||||||
|
|
||||||
// Optional arguments, two given, both valid but wrong order
|
|
||||||
stringArg1 = "";
|
|
||||||
intArg1 = 0;
|
|
||||||
doubleArg1 = 0;
|
|
||||||
result = runParser({ "999.999", "optional argument" }, [&](auto& parser) {
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(intArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(doubleArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringArg1, "999.999");
|
|
||||||
EXPECT_EQ(intArg1, 0);
|
|
||||||
EXPECT_EQ(doubleArg1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(BoolOptions)
|
|
||||||
{
|
|
||||||
// Short option
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
auto result = runParser({ "-b" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Short option, not given
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
|
|
||||||
// Long option
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "--bool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Long option, not given
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
|
|
||||||
// Allow both short and long option, provide short
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "-b" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Allow both short and long option, provide long
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "--bool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Allow both short and long option, provide both
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "-b", "--bool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', "bool", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(SingleRequiredStringOptions)
|
|
||||||
{
|
|
||||||
// Single required string short option
|
|
||||||
std::string stringOpt1 = "";
|
|
||||||
auto result = runParser({ "-s", "my-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string short option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-smy-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string short option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-s" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single required string short option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single required string long option
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string", "my-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string long option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string=my-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-required-argument");
|
|
||||||
|
|
||||||
// Single required string long option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single required string long option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(SingleOptionalStringOptions)
|
|
||||||
{
|
|
||||||
// Single optional string short option
|
|
||||||
std::string stringOpt1 = "";
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "-s", "my-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
EXPECT_EQ(stringArg1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string short option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-smy-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string short option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-s" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single optional string short option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single optional string long option
|
|
||||||
stringOpt1 = "";
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({ "--string", "my-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
EXPECT_EQ(stringArg1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string long option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string=my-optional-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "my-optional-argument");
|
|
||||||
|
|
||||||
// Single optional string long option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single optional string long option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::Optional);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(SingleNonRequiredStringOptions)
|
|
||||||
{
|
|
||||||
// Single non-required string short option
|
|
||||||
std::string stringOpt1 = "";
|
|
||||||
std::string stringArg1 = "";
|
|
||||||
auto result = runParser({ "-s", "my-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
EXPECT_EQ(stringArg1, "my-non-required-argument");
|
|
||||||
|
|
||||||
// Single non-required string short option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-smy-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string short option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "-s" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string short option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string long option
|
|
||||||
stringOpt1 = "";
|
|
||||||
stringArg1 = "";
|
|
||||||
result = runParser({ "--string", "my-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
parser.addArgument(stringArg1, nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
EXPECT_EQ(stringArg1, "my-non-required-argument");
|
|
||||||
|
|
||||||
// Single non-required string long option, given directly after
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string=my-non-required-argument" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string long option, empty given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({ "--string" }, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
|
|
||||||
// Single non-required string long option, not given
|
|
||||||
stringOpt1 = "";
|
|
||||||
result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(stringOpt1, '\0', "string", nullptr, nullptr, nullptr, Util::ArgParser::Required::No);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(NumberOptions)
|
|
||||||
{
|
|
||||||
// Required int short option
|
|
||||||
int intOpt1 = 0;
|
|
||||||
auto result = runParser({ "-i", "2147483647" }, [&](auto& parser) {
|
|
||||||
parser.addOption(intOpt1, 'i', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(intOpt1, 2147483647);
|
|
||||||
|
|
||||||
// Required int short option, overflown value given
|
|
||||||
intOpt1 = 0;
|
|
||||||
result = runParser({ "-i", "2147483648" }, [&](auto& parser) {
|
|
||||||
parser.addOption(intOpt1, 'i', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(intOpt1, 0);
|
|
||||||
|
|
||||||
// Required int short option, empty given
|
|
||||||
intOpt1 = 0;
|
|
||||||
result = runParser({ "-i" }, [&](auto& parser) {
|
|
||||||
parser.addOption(intOpt1, 'i', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(intOpt1, 0);
|
|
||||||
|
|
||||||
// Required unsigned int short option
|
|
||||||
unsigned int unsignedIntOpt1 = 0;
|
|
||||||
result = runParser({ "-u", "4294967295" }, [&](auto& parser) {
|
|
||||||
parser.addOption(unsignedIntOpt1, 'u', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(unsignedIntOpt1, 4294967295);
|
|
||||||
|
|
||||||
// Required unsigned int short option, overflown value given
|
|
||||||
unsignedIntOpt1 = 0;
|
|
||||||
result = runParser({ "-u", "4294967296" }, [&](auto& parser) {
|
|
||||||
parser.addOption(unsignedIntOpt1, 'u', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(unsignedIntOpt1, 0);
|
|
||||||
|
|
||||||
// Required unsigned int short option, empty given
|
|
||||||
unsignedIntOpt1 = 0;
|
|
||||||
result = runParser({ "-u" }, [&](auto& parser) {
|
|
||||||
parser.addOption(unsignedIntOpt1, 'u', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(unsignedIntOpt1, 0);
|
|
||||||
|
|
||||||
// Required double short option
|
|
||||||
double doubleOpt1 = 0;
|
|
||||||
result = runParser({ "-d", "999.999" }, [&](auto& parser) {
|
|
||||||
parser.addOption(doubleOpt1, 'd', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(doubleOpt1, 999.999);
|
|
||||||
|
|
||||||
// Required double short option, empty given
|
|
||||||
doubleOpt1 = 0;
|
|
||||||
result = runParser({ "-d" }, [&](auto& parser) {
|
|
||||||
parser.addOption(doubleOpt1, 'd', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(doubleOpt1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(VectorStringOptions)
|
|
||||||
{
|
|
||||||
// Required vector string short option, not given
|
|
||||||
std::vector<std::string> vectorOpt1 = {};
|
|
||||||
auto result = runParser({}, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 0);
|
|
||||||
|
|
||||||
// Required vector string short option, empty given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "-v" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 0);
|
|
||||||
|
|
||||||
// Required vector string short option, one given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "-v", "a vector argument!" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 1);
|
|
||||||
if (vectorOpt1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "a vector argument!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string short option, two given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "-v", "hello", "-v", "world" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 2);
|
|
||||||
if (vectorOpt1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "hello");
|
|
||||||
EXPECT_EQ(vectorOpt1[1], "world");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string short option, two given directly after
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "-vhello", "-vworld" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, 'v', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 2);
|
|
||||||
if (vectorOpt1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "hello");
|
|
||||||
EXPECT_EQ(vectorOpt1[1], "world");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string long option, empty given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "--vector" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 0);
|
|
||||||
|
|
||||||
// Required vector string long option, one given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "--vector", "a vector argument!" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 1);
|
|
||||||
if (vectorOpt1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "a vector argument!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string long option, two given
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "--vector", "hello", "--vector", "world" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 2);
|
|
||||||
if (vectorOpt1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorOpt1[0], "hello");
|
|
||||||
EXPECT_EQ(vectorOpt1[1], "world");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Required vector string long option, two given directly after
|
|
||||||
vectorOpt1 = {};
|
|
||||||
result = runParser({ "--vector=hello", "--vector=world" }, [&](auto& parser) {
|
|
||||||
parser.addOption(vectorOpt1, '\0', "vector", nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(vectorOpt1.size(), 2);
|
|
||||||
if (vectorOpt1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorOpt1.at(0), "hello");
|
|
||||||
EXPECT_EQ(vectorOpt1.at(1), "world");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(MultipleOptions)
|
|
||||||
{
|
|
||||||
// Both short options, second is required, with a non-option parameter in-between
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
std::string stringOpt1 = "";
|
|
||||||
std::vector<std::string> vectorArg1 = {};
|
|
||||||
auto result = runParser({ "-b", "something", "-s", "a-string-value" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(stringOpt1, 's', nullptr, nullptr, nullptr, nullptr, Util::ArgParser::Required::Yes);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(stringOpt1, "a-string-value");
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "something");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(StopOnDoubleDashOption)
|
|
||||||
{
|
|
||||||
// Bool short options, missing
|
|
||||||
// Expected: The bool options are interpreted as non-option parameters
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
bool boolOpt2 = false;
|
|
||||||
std::vector<std::string> vectorArg1 = {};
|
|
||||||
auto result = runParser({ "--", "-b", "-c" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 2);
|
|
||||||
if (vectorArg1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "-b");
|
|
||||||
EXPECT_EQ(vectorArg1.at(1), "-c");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool short options, one given
|
|
||||||
// Expected: boolOpt1 is set, one non-option parameter
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "-b", "--", "-c" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "-c");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool long options, missing
|
|
||||||
// Expected: The bool options are interpreted as non-option parameters
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "--", "--bool", "--cool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, '\0', "cool", nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 2);
|
|
||||||
if (vectorArg1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "--bool");
|
|
||||||
EXPECT_EQ(vectorArg1.at(1), "--cool");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bool long options, one given
|
|
||||||
// Expected: boolOpt1 is set, one non-option parameter
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "--bool", "--", "--cool" }, [&](auto& parser) {
|
|
||||||
parser.addOption(boolOpt1, '\0', "bool", nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, '\0', "cool", nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "--cool");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(StopOnFirstNonOption)
|
|
||||||
{
|
|
||||||
// Do not stop on first non-option; arguments are in correct order
|
|
||||||
// Expected: The bool options are set and one non-option parameter
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
bool boolOpt2 = false;
|
|
||||||
std::vector<std::string> vectorArg1 = {};
|
|
||||||
auto result = runParser({ "-b", "-c", "stopping" }, [&](auto& parser) {
|
|
||||||
parser.setStopParsingOnFirstNonOption(false);
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "stopping");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not stop on first non-option; arguments are in wrong order
|
|
||||||
// Expected: The bool options are set and one non-option parameter
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "-b", "stopping", "-c" }, [&](auto& parser) {
|
|
||||||
parser.setStopParsingOnFirstNonOption(false);
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "stopping");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop on first non option, arguments are in correct order
|
|
||||||
// Expected: The bool options are set and one non-option parameter
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "-b", "-c", "stopping" }, [&](auto& parser) {
|
|
||||||
parser.setStopParsingOnFirstNonOption(true);
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, true);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 1);
|
|
||||||
if (vectorArg1.size() == 1) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "stopping");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop on first non option, arguments are in wrong order
|
|
||||||
// Expected: boolOpt1 is set and the rest are non-option parameters
|
|
||||||
boolOpt1 = false;
|
|
||||||
boolOpt2 = false;
|
|
||||||
vectorArg1 = {};
|
|
||||||
result = runParser({ "-b", "stopping", "-c" }, [&](auto& parser) {
|
|
||||||
parser.setStopParsingOnFirstNonOption(true);
|
|
||||||
parser.addOption(boolOpt1, 'b', nullptr, nullptr, nullptr);
|
|
||||||
parser.addOption(boolOpt2, 'c', nullptr, nullptr, nullptr);
|
|
||||||
parser.addArgument(vectorArg1, nullptr, nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
EXPECT_EQ(boolOpt2, false);
|
|
||||||
EXPECT_EQ(vectorArg1.size(), 2);
|
|
||||||
if (vectorArg1.size() == 2) {
|
|
||||||
EXPECT_EQ(vectorArg1.at(0), "stopping");
|
|
||||||
EXPECT_EQ(vectorArg1.at(1), "-c");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(ExitOnFirstError)
|
|
||||||
{
|
|
||||||
// Do not stop on first error, one non-existing given
|
|
||||||
// Expected: parsing fails, boolOpt1 is set
|
|
||||||
bool boolOpt1 = false;
|
|
||||||
auto result = runParser({ "--this-doesnt-exist", "--this-exist" }, [&](auto& parser) {
|
|
||||||
parser.setExitOnFirstError(false);
|
|
||||||
parser.addOption(boolOpt1, '\0', "this-exist", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(boolOpt1, true);
|
|
||||||
|
|
||||||
// Stop on first error, one non-existing given
|
|
||||||
// Expected: parsing fails, boolOpt1 is not set
|
|
||||||
boolOpt1 = false;
|
|
||||||
result = runParser({ "--this-doesnt-exist", "--this-exist" }, [&](auto& parser) {
|
|
||||||
parser.setExitOnFirstError(true);
|
|
||||||
parser.addOption(boolOpt1, '\0', "this-exist", nullptr, nullptr);
|
|
||||||
});
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(boolOpt1, false);
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstddef> // size_t
|
|
||||||
#include <cstdint> // int32_t, uint32_t, int64_t
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "macro.h"
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/format/format.h"
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(FormatBasicTypes)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
|
|
||||||
result = Util::format("");
|
|
||||||
EXPECT_EQ(result, "");
|
|
||||||
|
|
||||||
result = Util::format("{}", nullptr);
|
|
||||||
EXPECT_EQ(result, "nullptr");
|
|
||||||
|
|
||||||
int* number = new int(3);
|
|
||||||
result = Util::format("{}", number);
|
|
||||||
EXPECT_EQ(result.substr(0, 2), "0x");
|
|
||||||
|
|
||||||
result = Util::format("{}", true);
|
|
||||||
EXPECT_EQ(result, "true");
|
|
||||||
|
|
||||||
result = Util::format("{}", false);
|
|
||||||
EXPECT_EQ(result, "false");
|
|
||||||
|
|
||||||
result = Util::format("{}", 'c');
|
|
||||||
EXPECT_EQ(result, "c");
|
|
||||||
|
|
||||||
const char* cString = "C string";
|
|
||||||
result = Util::format("{}", cString);
|
|
||||||
EXPECT_EQ(result, "C string");
|
|
||||||
|
|
||||||
std::string string = "string";
|
|
||||||
result = Util::format("{}", string);
|
|
||||||
EXPECT_EQ(result, "string");
|
|
||||||
|
|
||||||
std::string_view stringView = "string_view";
|
|
||||||
result = Util::format("{}", stringView);
|
|
||||||
EXPECT_EQ(result, "string_view");
|
|
||||||
|
|
||||||
result = Util::format("{} {}", "Hello", "World");
|
|
||||||
EXPECT_EQ(result, "Hello World");
|
|
||||||
|
|
||||||
result = Util::format("{{escaped braces}}");
|
|
||||||
EXPECT_EQ(result, "{escaped braces}");
|
|
||||||
|
|
||||||
result = Util::format("{{braces{}}}", "Something");
|
|
||||||
EXPECT_EQ(result, "{bracesSomething}");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE(FormatNumbers)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
|
|
||||||
int32_t i32 = 68766;
|
|
||||||
result = Util::format("{}", i32); // int
|
|
||||||
EXPECT_EQ(result, "68766");
|
|
||||||
|
|
||||||
uint32_t u32 = 123841; // unsigned int
|
|
||||||
result = Util::format("{}", u32);
|
|
||||||
EXPECT_EQ(result, "123841");
|
|
||||||
|
|
||||||
int64_t i64 = 237942768427; // long int
|
|
||||||
result = Util::format("{}", i64);
|
|
||||||
EXPECT_EQ(result, "237942768427");
|
|
||||||
|
|
||||||
size_t u64 = 1337; // long unsigned int
|
|
||||||
result = Util::format("{}", u64);
|
|
||||||
EXPECT_EQ(result, "1337");
|
|
||||||
|
|
||||||
float f32R = 245789.70000;
|
|
||||||
result = Util::format("{}", f32R);
|
|
||||||
EXPECT_EQ(result, "245789.703125");
|
|
||||||
|
|
||||||
float f32 = 45645.3233;
|
|
||||||
result = Util::format("{}", f32);
|
|
||||||
EXPECT_EQ(result, "45645.324219");
|
|
||||||
|
|
||||||
double f64 = 87522.300000000;
|
|
||||||
result = Util::format("{}", f64);
|
|
||||||
EXPECT_EQ(result, "87522.300000");
|
|
||||||
|
|
||||||
double pi = 3.14159265359;
|
|
||||||
result = Util::format("{:.15}", pi);
|
|
||||||
EXPECT_EQ(result, "3.141592653590000");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE(FormatContainers)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
|
|
||||||
std::vector<std::string> vector { "thing1", "thing2", "thing3" };
|
|
||||||
result = Util::format("{}", vector);
|
|
||||||
EXPECT_EQ(result, R"({
|
|
||||||
thing1,
|
|
||||||
thing2,
|
|
||||||
thing3
|
|
||||||
})");
|
|
||||||
|
|
||||||
std::map<std::string, int> map { { "thing3", 3 }, { "thing2", 2 }, { "thing1", 1 } };
|
|
||||||
result = Util::format("{}", map);
|
|
||||||
EXPECT_EQ(result, R"({
|
|
||||||
"thing1": 1,
|
|
||||||
"thing2": 2,
|
|
||||||
"thing3": 3
|
|
||||||
})");
|
|
||||||
|
|
||||||
// Multidimensional containers arent supported,
|
|
||||||
// the user should write a customization point
|
|
||||||
std::vector<std::vector<std::string>> twoDimensionalVector {
|
|
||||||
{ "thing1", "thing2", "thing3" },
|
|
||||||
{ "thing1", "thing2", "thing3" }
|
|
||||||
};
|
|
||||||
result = Util::format("{}", twoDimensionalVector);
|
|
||||||
EXPECT_EQ(result, R"({
|
|
||||||
{
|
|
||||||
thing1,
|
|
||||||
thing2,
|
|
||||||
thing3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
thing1,
|
|
||||||
thing2,
|
|
||||||
thing3
|
|
||||||
}
|
|
||||||
})");
|
|
||||||
}
|
|
||||||
@@ -1,570 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2022 Riyyi
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <cstddef> // nullptr_t
|
|
||||||
#include <cstdint> // uint32_t
|
|
||||||
#include <functional> // function
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "macro.h"
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/json/array.h"
|
|
||||||
#include "util/json/job.h"
|
|
||||||
#include "util/json/json.h"
|
|
||||||
#include "util/json/lexer.h"
|
|
||||||
#include "util/json/parser.h"
|
|
||||||
#include "util/json/serializer.h"
|
|
||||||
|
|
||||||
#define DONT_PRINT_PARSER_ERRORS
|
|
||||||
|
|
||||||
#ifndef DONT_PRINT_PARSER_ERRORS
|
|
||||||
#define EXEC(x) x
|
|
||||||
#else
|
|
||||||
#define EXEC(x) \
|
|
||||||
stderr = Test::TestSuite::the().outputNull(); \
|
|
||||||
x; \
|
|
||||||
stderr = Test::TestSuite::the().outputErr();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::vector<Util::JSON::Token> lex(const std::string& input)
|
|
||||||
{
|
|
||||||
EXEC(
|
|
||||||
Util::JSON::Job job(input);
|
|
||||||
Util::JSON::Lexer lexer(&job);
|
|
||||||
lexer.analyze(););
|
|
||||||
return *job.tokens();
|
|
||||||
}
|
|
||||||
|
|
||||||
Util::Json parse(const std::string& input)
|
|
||||||
{
|
|
||||||
EXEC(
|
|
||||||
Util::JSON::Job job(input);
|
|
||||||
Util::JSON::Lexer lexer(&job);
|
|
||||||
lexer.analyze(););
|
|
||||||
|
|
||||||
if (!job.success()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
EXEC(
|
|
||||||
Util::JSON::Parser parser(&job);
|
|
||||||
Util::Json json = parser.parse(););
|
|
||||||
|
|
||||||
if (!job.success()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string serialize(const std::string& input, uint32_t indent = 0)
|
|
||||||
{
|
|
||||||
EXEC(
|
|
||||||
auto json = Util::Json::parse(input););
|
|
||||||
return json.dump(indent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(JsonLexer)
|
|
||||||
{
|
|
||||||
std::vector<Util::JSON::Token> tokens;
|
|
||||||
|
|
||||||
// Literal
|
|
||||||
|
|
||||||
tokens = lex("true");
|
|
||||||
EXPECT_EQ(tokens.size(), 1);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "true");
|
|
||||||
EXPECT(tokens[0].type == Util::JSON::Token::Type::Literal);
|
|
||||||
|
|
||||||
tokens = lex("false");
|
|
||||||
EXPECT_EQ(tokens.size(), 1);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "false");
|
|
||||||
EXPECT(tokens[0].type == Util::JSON::Token::Type::Literal);
|
|
||||||
|
|
||||||
tokens = lex("null");
|
|
||||||
EXPECT_EQ(tokens.size(), 1);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "null");
|
|
||||||
EXPECT(tokens[0].type == Util::JSON::Token::Type::Literal);
|
|
||||||
|
|
||||||
// Number
|
|
||||||
|
|
||||||
tokens = lex("3.14");
|
|
||||||
EXPECT_EQ(tokens.size(), 1);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "3.14");
|
|
||||||
EXPECT(tokens[0].type == Util::JSON::Token::Type::Number);
|
|
||||||
|
|
||||||
tokens = lex("-3.14e+2");
|
|
||||||
EXPECT_EQ(tokens.size(), 1);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "-3.14e+2");
|
|
||||||
EXPECT(tokens[0].type == Util::JSON::Token::Type::Number);
|
|
||||||
|
|
||||||
tokens = lex("+3.14");
|
|
||||||
EXPECT_EQ(tokens.size(), 1);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "+");
|
|
||||||
EXPECT(tokens[0].type == Util::JSON::Token::Type::None);
|
|
||||||
|
|
||||||
// String
|
|
||||||
|
|
||||||
tokens = lex(R"("a string")");
|
|
||||||
EXPECT_EQ(tokens.size(), 1);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "a string");
|
|
||||||
EXPECT(tokens[0].type == Util::JSON::Token::Type::String);
|
|
||||||
|
|
||||||
tokens = lex(R"("a string""another string")");
|
|
||||||
EXPECT_EQ(tokens.size(), 2);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "a string");
|
|
||||||
EXPECT_EQ(tokens[1].symbol, "another string");
|
|
||||||
EXPECT(tokens[0].type == Util::JSON::Token::Type::String);
|
|
||||||
|
|
||||||
tokens = lex("\"a string\nwill break on the newline symbol\"");
|
|
||||||
EXPECT_EQ(tokens.size(), 1);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "a string");
|
|
||||||
EXPECT(tokens[0].type == Util::JSON::Token::Type::String);
|
|
||||||
|
|
||||||
// Array
|
|
||||||
|
|
||||||
tokens = lex("[]");
|
|
||||||
EXPECT_EQ(tokens.size(), 2);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "[");
|
|
||||||
EXPECT_EQ(tokens[1].symbol, "]");
|
|
||||||
|
|
||||||
tokens = lex("[\n\n\n]");
|
|
||||||
EXPECT_EQ(tokens.size(), 2);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "[");
|
|
||||||
EXPECT_EQ(tokens[1].symbol, "]");
|
|
||||||
|
|
||||||
// Object
|
|
||||||
|
|
||||||
tokens = lex("{}");
|
|
||||||
EXPECT_EQ(tokens.size(), 2);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "{");
|
|
||||||
EXPECT_EQ(tokens[1].symbol, "}");
|
|
||||||
|
|
||||||
tokens = lex("{\n\n\n}");
|
|
||||||
EXPECT_EQ(tokens.size(), 2);
|
|
||||||
EXPECT_EQ(tokens[0].symbol, "{");
|
|
||||||
EXPECT_EQ(tokens[1].symbol, "}");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE(JsonParser)
|
|
||||||
{
|
|
||||||
Util::Json json;
|
|
||||||
|
|
||||||
json = parse("null");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse("true");
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Bool);
|
|
||||||
|
|
||||||
json = parse("false");
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Bool);
|
|
||||||
|
|
||||||
json = parse("3.14");
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Number);
|
|
||||||
|
|
||||||
json = parse(R"("a string")");
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::String);
|
|
||||||
|
|
||||||
// Array
|
|
||||||
|
|
||||||
json = parse("[");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse("[ 123");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse("[ 123,");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse("[ 123, ]");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse("[ 123 456 ]");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse("[]");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Array);
|
|
||||||
|
|
||||||
json = parse(R"([ "element", 3.14 ])");
|
|
||||||
EXPECT_EQ(json.size(), 2);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Array);
|
|
||||||
|
|
||||||
// Object
|
|
||||||
|
|
||||||
json = parse("{");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse(R"({ "name")");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse(R"({ "name":)");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse(R"({ "name":,)");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse(R"({ "name":"value")");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse(R"({ "name":"value",)");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse(R"({ "name":"value", })");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse(R"({ "name" "value" })");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse(R"({ 123 })");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse("{}");
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Object);
|
|
||||||
|
|
||||||
json = parse(R"({ "name": "value", "name2": 3.14 })");
|
|
||||||
EXPECT_EQ(json.size(), 2);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Object);
|
|
||||||
|
|
||||||
// Multiple root elements
|
|
||||||
|
|
||||||
json = parse("54 false");
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse("3.14, 666");
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = parse("true\nfalse");
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE(JsonToJsonValue)
|
|
||||||
{
|
|
||||||
Util::Json json;
|
|
||||||
|
|
||||||
json = {};
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = nullptr;
|
|
||||||
EXPECT_EQ(json.size(), 0);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Null);
|
|
||||||
|
|
||||||
json = true;
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Bool);
|
|
||||||
|
|
||||||
json = false;
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Bool);
|
|
||||||
|
|
||||||
json = 666;
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Number);
|
|
||||||
|
|
||||||
json = 3.14;
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Number);
|
|
||||||
|
|
||||||
const char* characters = "my string";
|
|
||||||
json = characters;
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::String);
|
|
||||||
|
|
||||||
std::string string = "my string";
|
|
||||||
json = string;
|
|
||||||
EXPECT_EQ(json.size(), 1);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::String);
|
|
||||||
|
|
||||||
// Nested Array with multiple types
|
|
||||||
json = { "element", 3.14, true, nullptr, { "nested element", { "more nesting", { 1, 2, 3, "yes" } } } };
|
|
||||||
EXPECT_EQ(json.size(), 5);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Array);
|
|
||||||
|
|
||||||
// Nested Object with multiple types
|
|
||||||
json = { { "name", "value" }, { "name2", 3.14 }, { "name3", true }, { "name4", nullptr }, { "name5", { { "nested name", "value" } } } };
|
|
||||||
EXPECT_EQ(json.size(), 5);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Object);
|
|
||||||
|
|
||||||
// Array with singular type
|
|
||||||
std::vector<std::string> vector = { "element", "element2", "element3" };
|
|
||||||
json = vector;
|
|
||||||
EXPECT_EQ(json.size(), 3);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Array);
|
|
||||||
|
|
||||||
// Object with singular type
|
|
||||||
std::map<std::string, std::string> map = { { "name", "value" }, { "name2", "value2" } };
|
|
||||||
json = map;
|
|
||||||
EXPECT_EQ(json.size(), 2);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Object);
|
|
||||||
|
|
||||||
// Object with singular type
|
|
||||||
std::unordered_map<std::string, std::string> unorderedMap = { { "name", "value" }, { "name2", "value2" } };
|
|
||||||
json = unorderedMap;
|
|
||||||
EXPECT_EQ(json.size(), 2);
|
|
||||||
EXPECT_EQ(json.type(), Util::Json::Type::Object);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE(JsonFromJsonValue)
|
|
||||||
{
|
|
||||||
Util::Json json;
|
|
||||||
|
|
||||||
json = nullptr;
|
|
||||||
EXPECT_EQ(json.get<std::nullptr_t>(), nullptr);
|
|
||||||
|
|
||||||
json = true;
|
|
||||||
EXPECT_EQ(json.get<bool>(), true);
|
|
||||||
|
|
||||||
json = false;
|
|
||||||
EXPECT_EQ(json.get<bool>(), false);
|
|
||||||
|
|
||||||
json = 666;
|
|
||||||
EXPECT_EQ(json.get<int>(), 666);
|
|
||||||
|
|
||||||
json = 3.14;
|
|
||||||
EXPECT_EQ(json.get<double>(), 3.14);
|
|
||||||
|
|
||||||
std::string string;
|
|
||||||
json = "my string";
|
|
||||||
json.getTo(string);
|
|
||||||
EXPECT_EQ(string, "my string");
|
|
||||||
EXPECT_EQ(json.get<std::string>(), "my string");
|
|
||||||
|
|
||||||
// Array with singular type
|
|
||||||
json = { "element", "element2" };
|
|
||||||
EXPECT_EQ(json[0].get<std::string>(), "element");
|
|
||||||
EXPECT_EQ(json.at(1).get<std::string>(), "element2");
|
|
||||||
auto array = json.get<std::vector<std::string>>();
|
|
||||||
EXPECT_EQ(array.size(), 2);
|
|
||||||
EXPECT_EQ(array[0], "element");
|
|
||||||
EXPECT_EQ(array[1], "element2");
|
|
||||||
|
|
||||||
// Array with multiple types
|
|
||||||
json = { "string", 3.14, true, nullptr };
|
|
||||||
EXPECT_EQ(json[0].get<std::string>(), "string");
|
|
||||||
EXPECT_EQ(json.at(1).get<double>(), 3.14);
|
|
||||||
EXPECT_EQ(json[2].get<bool>(), true);
|
|
||||||
EXPECT_EQ(json[3].get<std::nullptr_t>(), nullptr);
|
|
||||||
auto valueArray = json.get<std::vector<Util::Json>>();
|
|
||||||
EXPECT_EQ(valueArray.size(), 4);
|
|
||||||
EXPECT_EQ(valueArray[0].get<std::string>(), "string");
|
|
||||||
EXPECT_EQ(valueArray[1].get<double>(), 3.14);
|
|
||||||
EXPECT_EQ(valueArray[2].get<bool>(), true);
|
|
||||||
EXPECT_EQ(valueArray[3].get<std::nullptr_t>(), nullptr);
|
|
||||||
|
|
||||||
// Nested Array with multiple types
|
|
||||||
json = {
|
|
||||||
"value",
|
|
||||||
{
|
|
||||||
"thing",
|
|
||||||
666,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{
|
|
||||||
3.14,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
EXPECT_EQ(json[0].get<std::string>(), "value");
|
|
||||||
EXPECT_EQ(json.at(1)[0].get<std::string>(), "thing");
|
|
||||||
EXPECT_EQ(json[1].at(1).get<int>(), 666);
|
|
||||||
EXPECT_EQ(json[2][0][0].get<double>(), 3.14);
|
|
||||||
|
|
||||||
// Object with singular type
|
|
||||||
json = { { "name", "value" }, { "name2", "value2" } };
|
|
||||||
EXPECT_EQ(json["name"].get<std::string>(), "value");
|
|
||||||
EXPECT_EQ(json.at("name2").get<std::string>(), "value2");
|
|
||||||
auto object = json.get<std::map<std::string, std::string>>();
|
|
||||||
EXPECT_EQ(object.size(), 2);
|
|
||||||
EXPECT_EQ(object["name"], "value");
|
|
||||||
EXPECT_EQ(object["name2"], "value2");
|
|
||||||
auto unorderedObject = json.get<std::unordered_map<std::string, std::string>>();
|
|
||||||
EXPECT_EQ(unorderedObject.size(), 2);
|
|
||||||
EXPECT_EQ(unorderedObject["name"], "value");
|
|
||||||
EXPECT_EQ(unorderedObject["name2"], "value2");
|
|
||||||
|
|
||||||
// Object with multiple types
|
|
||||||
json = { { "name", "value" }, { "name2", 3.14 }, { "name3", true }, { "name4", nullptr } };
|
|
||||||
EXPECT_EQ(json["name"].get<std::string>(), "value");
|
|
||||||
EXPECT_EQ(json.at("name2").get<double>(), 3.14);
|
|
||||||
EXPECT_EQ(json["name3"].get<bool>(), true);
|
|
||||||
EXPECT_EQ(json["name4"].get<std::nullptr_t>(), nullptr);
|
|
||||||
auto valueObject = json.get<std::map<std::string, Util::Json>>();
|
|
||||||
EXPECT_EQ(valueObject.size(), 4);
|
|
||||||
EXPECT_EQ(valueObject["name"].get<std::string>(), "value");
|
|
||||||
EXPECT_EQ(valueObject["name2"].get<double>(), 3.14);
|
|
||||||
EXPECT_EQ(valueObject["name3"].get<bool>(), true);
|
|
||||||
EXPECT_EQ(valueObject["name4"].get<std::nullptr_t>(), nullptr);
|
|
||||||
|
|
||||||
// Nested Object with multiple types
|
|
||||||
json = {
|
|
||||||
{
|
|
||||||
"name",
|
|
||||||
"value",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nest 1-deep",
|
|
||||||
{ {
|
|
||||||
"number",
|
|
||||||
1,
|
|
||||||
} },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"nest 2-deep",
|
|
||||||
{ {
|
|
||||||
"nest 1-deep",
|
|
||||||
{ {
|
|
||||||
"bool",
|
|
||||||
true,
|
|
||||||
} },
|
|
||||||
} },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
EXPECT_EQ(json["name"].get<std::string>(), "value");
|
|
||||||
EXPECT_EQ(json["nest 1-deep"]["number"].get<int>(), 1);
|
|
||||||
EXPECT_EQ(json["nest 2-deep"]["nest 1-deep"]["bool"].get<bool>(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE(JsonImplicitConversion)
|
|
||||||
{
|
|
||||||
Util::Json array;
|
|
||||||
array[0];
|
|
||||||
EXPECT_EQ(array.type(), Util::Json::Type::Array);
|
|
||||||
|
|
||||||
Util::Json arrayEmplace;
|
|
||||||
arrayEmplace.emplace_back("element");
|
|
||||||
arrayEmplace.emplace_back({ "nested element" });
|
|
||||||
EXPECT_EQ(arrayEmplace.type(), Util::Json::Type::Array);
|
|
||||||
EXPECT_EQ(arrayEmplace[1].type(), Util::Json::Type::Array);
|
|
||||||
|
|
||||||
Util::Json object;
|
|
||||||
object[""];
|
|
||||||
EXPECT_EQ(object.type(), Util::Json::Type::Object);
|
|
||||||
|
|
||||||
Util::Json objectEmplace;
|
|
||||||
objectEmplace.emplace("name", "value");
|
|
||||||
objectEmplace.emplace("name2", { { "nested name", "value" } });
|
|
||||||
EXPECT_EQ(objectEmplace.type(), Util::Json::Type::Object);
|
|
||||||
EXPECT_EQ(objectEmplace["name2"].type(), Util::Json::Type::Object);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE(JsonSerializer)
|
|
||||||
{
|
|
||||||
EXPECT_EQ(serialize(""), "null");
|
|
||||||
EXPECT_EQ(serialize("null"), "null");
|
|
||||||
EXPECT_EQ(serialize("true"), "true");
|
|
||||||
EXPECT_EQ(serialize("false"), "false");
|
|
||||||
EXPECT_EQ(serialize("3.14"), "3.14");
|
|
||||||
EXPECT_EQ(serialize(R"("string")"), R"("string")");
|
|
||||||
|
|
||||||
EXPECT_EQ(serialize("\n\n\n"), "null");
|
|
||||||
EXPECT_EQ(serialize("null\n"), "null");
|
|
||||||
EXPECT_EQ(serialize("true\n"), "true");
|
|
||||||
EXPECT_EQ(serialize("false\n"), "false");
|
|
||||||
EXPECT_EQ(serialize("3.14\n"), "3.14");
|
|
||||||
// clang-format off
|
|
||||||
EXPECT_EQ(serialize(R"("string")" "\n"), R"("string")");
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
EXPECT_EQ(serialize("[\n\n\n]"), "[]");
|
|
||||||
EXPECT_EQ(serialize("[null]"), "[null]");
|
|
||||||
EXPECT_EQ(serialize("[true]"), "[true]");
|
|
||||||
EXPECT_EQ(serialize("[false]"), "[false]");
|
|
||||||
EXPECT_EQ(serialize("[3.14]"), "[3.14]");
|
|
||||||
EXPECT_EQ(serialize(R"(["string"])"), R"(["string"])");
|
|
||||||
|
|
||||||
EXPECT_EQ(serialize("[\n\n\n]", 4), "[\n]");
|
|
||||||
EXPECT_EQ(serialize("[null]", 4), "[\n null\n]");
|
|
||||||
EXPECT_EQ(serialize("[true]", 4), "[\n true\n]");
|
|
||||||
EXPECT_EQ(serialize("[false]", 4), "[\n false\n]");
|
|
||||||
EXPECT_EQ(serialize("[3.14]", 4), "[\n 3.14\n]");
|
|
||||||
// clang-format off
|
|
||||||
EXPECT_EQ(serialize(R"(["string"])", 4), "[\n " R"("string")" "\n]");
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
// Check for trailing comma on last array element
|
|
||||||
EXPECT_EQ(serialize(R"([1])"), R"([1])");
|
|
||||||
EXPECT_EQ(serialize(R"([1,2])"), R"([1,2])");
|
|
||||||
EXPECT_EQ(serialize(R"([1,2,3])"), R"([1,2,3])");
|
|
||||||
|
|
||||||
// Check for trailing comma on last object member
|
|
||||||
EXPECT_EQ(serialize(R"({"n1":"v1"})"), R"({"n1":"v1"})");
|
|
||||||
EXPECT_EQ(serialize(R"({"n1":"v1", "n2":"v2"})"), R"({"n1":"v1","n2":"v2"})");
|
|
||||||
EXPECT_EQ(serialize(R"({"n1":"v1", "n2":"v2", "n3":"v3"})"), R"({"n1":"v1","n2":"v2","n3":"v3"})");
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
EXPECT_EQ(serialize(R"({
|
|
||||||
"object member one": [
|
|
||||||
"array element one"
|
|
||||||
],
|
|
||||||
"object member two": [
|
|
||||||
"array element one",
|
|
||||||
"array element two"
|
|
||||||
],
|
|
||||||
"object member three": [
|
|
||||||
"array element one",
|
|
||||||
2,
|
|
||||||
3.0,
|
|
||||||
4.56,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
],
|
|
||||||
"object member four": 3.14,
|
|
||||||
"object member five": "value five",
|
|
||||||
"object member six": null,
|
|
||||||
"object member seven": { "no": 0 }
|
|
||||||
})", 4), R"({
|
|
||||||
"object member five": "value five",
|
|
||||||
"object member four": 3.14,
|
|
||||||
"object member one": [
|
|
||||||
"array element one"
|
|
||||||
],
|
|
||||||
"object member seven": {
|
|
||||||
"no": 0
|
|
||||||
},
|
|
||||||
"object member six": null,
|
|
||||||
"object member three": [
|
|
||||||
"array element one",
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4.56,
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
null
|
|
||||||
],
|
|
||||||
"object member two": [
|
|
||||||
"array element one",
|
|
||||||
"array element two"
|
|
||||||
]
|
|
||||||
})");
|
|
||||||
// clang-format on
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#include <string>
|
|
||||||
|
|
||||||
#include "macro.h"
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/shell.h"
|
|
||||||
|
|
||||||
bool runShell(const char* command, std::string* output = nullptr)
|
|
||||||
{
|
|
||||||
stdout = Test::TestSuite::the().outputNull();
|
|
||||||
|
|
||||||
Util::Shell $;
|
|
||||||
auto exec = $(command);
|
|
||||||
if (output) {
|
|
||||||
*output = exec.output();
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout = Test::TestSuite::the().outputStd();
|
|
||||||
return !exec.status() ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(ShellCommand)
|
|
||||||
{
|
|
||||||
// Pipe test, grep section of the echo command, return true
|
|
||||||
std::string output = "";
|
|
||||||
auto result = runShell("echo 'Hello World!' | grep --only-matching 'Hello'", &output);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello\n");
|
|
||||||
|
|
||||||
// Return false
|
|
||||||
result = runShell("exit 1");
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
|
|
||||||
// Return false
|
|
||||||
result = runShell("failure() { return 1; }; failure");
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
#include <functional> // function
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "macro.h"
|
|
||||||
#include "testcase.h"
|
|
||||||
#include "testsuite.h"
|
|
||||||
#include "util/system.h"
|
|
||||||
|
|
||||||
bool runSystem(std::function<Util::System(Util::System&)> commands, std::string* output = nullptr, std::string* error = nullptr)
|
|
||||||
{
|
|
||||||
Util::System system;
|
|
||||||
auto exec = commands(system);
|
|
||||||
if (output) {
|
|
||||||
*output = exec.output();
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
*error = exec.error();
|
|
||||||
}
|
|
||||||
|
|
||||||
return !exec.status() ? true : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// -----------------------------------------
|
|
||||||
|
|
||||||
TEST_CASE(SystemCommand)
|
|
||||||
{
|
|
||||||
// Regular echo command, return true
|
|
||||||
std::string output = "";
|
|
||||||
std::string error = "";
|
|
||||||
auto result = runSystem([&](auto& $) {
|
|
||||||
return $("echo Hello World!")();
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello World!\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// Apend output of two echo commands, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("echo -n Hello ") + $("echo -n World!");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello World!");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// Pipe test, grep section of the echo command, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("echo Hello World!") | $("grep --only-matching Hello");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// Run all commands until first false, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("echo Hello") && $("echo World!");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello\nWorld!\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// Run all commands until first true, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("echo Hello") || $("echo World!");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "Hello\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// And plus pipe test, grep the middle word, return true
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return ($("echo -n one ") && $("echo -n two ") && $("echo -n three")) | $("grep --only-matching two");
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, true);
|
|
||||||
EXPECT_EQ(output, "two\n");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
|
|
||||||
// FIXME waitpid does not seem to get the right exit status here
|
|
||||||
// Return false
|
|
||||||
output = "";
|
|
||||||
error = "";
|
|
||||||
result = runSystem([&](auto& $) {
|
|
||||||
return $("exit 1")();
|
|
||||||
}, &output, &error);
|
|
||||||
EXPECT_EQ(result, false);
|
|
||||||
EXPECT_EQ(output, "");
|
|
||||||
EXPECT_EQ(error, "");
|
|
||||||
}
|
|
||||||
+1
Submodule vendor/ruc added at 243442a3b6
Reference in New Issue
Block a user