Compare commits

...

8 commits
0.6 ... master

Author SHA1 Message Date
1523f83a6b
Restructure ExtractIntent
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-02 16:27:21 +01:00
92ac6586de
Restructure CompressSingleIntent 2024-03-02 16:27:21 +01:00
c8e6f51e1b
Reformat UserIntent
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-28 18:55:09 +01:00
1aea5cd924
Cleanup UserIntent
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-28 01:06:20 +01:00
293ee6e1cc
Get interactive mode from correct argument
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-27 22:44:00 +01:00
5d9856c658
Control log level via -v option
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is running
Allow the user to control the log level via the conventional `-v`
command line option. Multiplying this option-as per convention-increases
the verbosity.

This is an additional way to set the log level, in addition to the
existing:
- environment variable
- build type (debug vs release)

Given it is the most intentional and direct way to set the log level it
has precedence over all other options.
2024-02-27 21:48:07 +01:00
65053bbcb9
Cleanup some unused includes 2024-02-27 21:47:34 +01:00
c1506c4547
Add more editors/environments to .gitignore 2024-02-27 21:46:11 +01:00
7 changed files with 485 additions and 194 deletions

247
.gitignore vendored
View file

@ -2,11 +2,11 @@
build/ build/
target/ target/
compile_commands.json compile_commands.json
.vscode
.ccls-cache .ccls-cache
.idea/codeStyles/**
# Created by https://www.gitignore.io/api/vim,c++,emacs,ninja # Created by https://www.toptal.com/developers/gitignore/api/c++,vim,emacs,linux,macos,ninja,windows,jetbrains+all,clion+all,visualstudiocode
# Edit at https://www.gitignore.io/?templates=vim,c++,emacs,ninja # Edit at https://www.toptal.com/developers/gitignore?templates=c++,vim,emacs,linux,macos,ninja,windows,jetbrains+all,clion+all,visualstudiocode
### C++ ### ### C++ ###
# Prerequisites # Prerequisites
@ -42,6 +42,94 @@ compile_commands.json
*.out *.out
*.app *.app
### CLion+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### CLion+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
### Emacs ### ### Emacs ###
# -*- mode: gitignore; -*- # -*- mode: gitignore; -*-
*~ *~
@ -93,6 +181,108 @@ flycheck_*.el
/network-security.data /network-security.data
### JetBrains+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
# AWS User-specific
# Generated files
# Sensitive or high-churn files
# Gradle
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
# Mongo Explorer plugin
# File-based project format
# IntelliJ
# mpeltonen/sbt-idea plugin
# JIRA plugin
# Cursive Clojure plugin
# SonarLint plugin
# Crashlytics plugin (for Android Studio and IntelliJ)
# Editor-based Rest Client
# Android studio 3.1+ serialized cache file
### JetBrains+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
### Linux ###
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### macOS Patch ###
# iCloud generated files
*.icloud
### Ninja ### ### Ninja ###
.ninja_deps .ninja_deps
.ninja_log .ninja_log
@ -100,6 +290,7 @@ flycheck_*.el
### Vim ### ### Vim ###
# Swap # Swap
[._]*.s[a-v][a-z] [._]*.s[a-v][a-z]
!*.svg # comment out if you don't need vector files
[._]*.sw[a-p] [._]*.sw[a-p]
[._]s[a-rt-v][a-z] [._]s[a-rt-v][a-z]
[._]ss[a-gi-z] [._]ss[a-gi-z]
@ -111,14 +302,54 @@ Sessionx.vim
# Temporary # Temporary
.netrwhist .netrwhist
# Auto-generated tag files # Auto-generated tag files
tags tags
# Persistent undo # Persistent undo
[._]*.un~ [._]*.un~
# Coc configuration directory ### VisualStudioCode ###
.vim .vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# End of https://www.gitignore.io/api/vim,c++,emacs,ninja # Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.toptal.com/developers/gitignore/api/c++,vim,emacs,linux,macos,ninja,windows,jetbrains+all,clion+all,visualstudiocode

View file

@ -1,190 +1,226 @@
#include "UserIntent.hpp" #include "UserIntent.hpp"
#include <spdlog/spdlog.h>
#include <algorithm> #include <algorithm>
#include <filesystem> #include <filesystem>
#include <spdlog/spdlog.h>
#include "Archiver.hpp" #include "Archiver.hpp"
namespace xwim { namespace xwim {
unique_ptr<UserIntent> make_compress_intent(const UserOpt &userOpt) {
if (userOpt.paths.size() == 1) {
return make_unique<CompressSingleIntent>(
CompressSingleIntent{*userOpt.paths.begin(), userOpt.out});
}
unique_ptr<UserIntent> make_intent(const UserOpt &userOpt) { if (!userOpt.out.has_value()) {
if (userOpt.wants_compress() && userOpt.wants_extract()) { throw XwimError("Cannot guess output for multiple targets");
throw XwimError("Cannot compress and extract simultaneously"); }
}
if(userOpt.paths.size() == 0) {
throw XwimError("No input given...");
}
// compression intent explicitly specified return make_unique<CompressManyIntent>(
if (userOpt.wants_compress()) { CompressManyIntent{userOpt.paths, userOpt.out.value()});
if (userOpt.paths.size() == 1) { }
return make_unique<CompressSingleIntent>(
CompressSingleIntent{
*userOpt.paths.begin(),
userOpt.out
});
}
if (!userOpt.out.has_value()) { unique_ptr<UserIntent> make_extract_intent(const UserOpt &userOpt) {
throw XwimError("Cannot guess output for multiple targets"); for (const path &p : userOpt.paths) {
} if (!can_handle_archive(p)) {
throw XwimError("Cannot extract path {}", p);
}
}
return make_unique<CompressManyIntent>( return make_unique<ExtractIntent>(ExtractIntent{userOpt.paths, userOpt.out});
CompressManyIntent{ }
userOpt.paths,
userOpt.out.value()
});
}
// extraction intent explicitly specified unique_ptr<UserIntent> try_infer_compress_intent(const UserOpt &userOpt) {
if (userOpt.wants_extract()) { if (!userOpt.out.has_value()) {
for (path p: userOpt.paths) { spdlog::debug("No <out> provided");
if (!can_handle_archive(p)) { if (userOpt.paths.size() != 1) {
throw XwimError("Cannot extract path {}", p); spdlog::debug(
} "Not a single-path compression. Cannot guess <out> for many-path "
} "compression");
return nullptr;
return make_unique<ExtractIntent>(
ExtractIntent{
userOpt.paths,
userOpt.out
});
}
// no intent explicitly specified, try to infer from input
bool can_extract_all = std::all_of(
userOpt.paths.begin(), userOpt.paths.end(),
[](path path) {
return can_handle_archive(path);
});
bool is_out_archive = userOpt.out.has_value() && can_handle_archive(userOpt.out.value());
// out is explicitly specified and an archive, assume we want compression
if(is_out_archive) {
if(userOpt.paths.size() == 1) {
return make_unique<CompressSingleIntent>(
CompressSingleIntent{
*userOpt.paths.begin(),
userOpt.out
});
}
return make_unique<CompressManyIntent>(
CompressManyIntent{
userOpt.paths,
userOpt.out.value() // this is ok is_out_archive checks for has_value()
}
);
}
// all inputs are extractable archives, assume extraction intent
if (can_extract_all) {
return make_unique<ExtractIntent>(
ExtractIntent{
userOpt.paths,
userOpt.out
});
}
// at this point all we can hope for is that the intention is a single-path compression:
// we don't know how to extract it; we don't know (and can't guess) output for many-path compression;
if(userOpt.paths.size() == 1) {
return make_unique<CompressSingleIntent>(
CompressSingleIntent{
*userOpt.paths.begin(),
userOpt.out
});
}
throw XwimError("Cannot guess intent");
} }
spdlog::debug("Only one <path> provided. Assume single-path compression.");
return make_unique<CompressSingleIntent>(
CompressSingleIntent{*userOpt.paths.begin(), userOpt.out});
}
void ExtractIntent::execute() { spdlog::debug("<out> provided: {}", userOpt.out.value());
bool has_out = this->out.has_value(); if (can_handle_archive(userOpt.out.value())) {
bool is_single = this->archives.size() == 1; spdlog::debug("{} given and a known archive format, assume compression",
userOpt.out.value());
return make_compress_intent(userOpt);
}
for (path p: this->archives) { spdlog::debug(
unique_ptr<Archiver> archiver = make_archiver(p); "Cannot compress multiple paths without a user-provided output archive");
path out; return nullptr;
}
if(has_out) { unique_ptr<UserIntent> try_infer_extract_intent(const UserOpt &userOpt) {
if(is_single) { // just dump content of archive into `out` bool can_extract_all =
std::filesystem::create_directories(this->out.value()); std::all_of(userOpt.paths.begin(), userOpt.paths.end(),
out = this->out.value(); [](const path &path) { return can_handle_archive(path); });
} else { // create an `out` folder and extract inside there
std::filesystem::create_directories(this->out.value());
out = this->out.value() / strip_archive_extension(p);
}
} else {
out = std::filesystem::current_path() / strip_archive_extension(p);
std::filesystem::create_directories(out);
}
archiver->extract(p, out); if (!can_extract_all) {
spdlog::debug(
"Cannot extract all provided <paths>. Assume this is not an "
"extraction.");
for (const path &p : userOpt.paths) {
if (!can_handle_archive(p)) {
spdlog::debug("Cannot handle {}", p);
}
}
// move folder if only one entry and that entries name is already return nullptr;
// the stripped archive name }
auto dit = std::filesystem::directory_iterator(out);
if(dit == std::filesystem::directory_iterator()) { if (userOpt.out.has_value() && can_handle_archive(userOpt.out.value())) {
spdlog::debug("Archive is empty"); spdlog::debug(
} else if(is_directory(dit->path())){ "Could extract all provided <paths>. But also {} looks like an "
auto first_path = dit->path(); "archive. Ambiguous intent. Assume this is not an extraction.",
auto next_entry = next(dit); userOpt.out.value());
return nullptr;
}
if(next_entry == std::filesystem::directory_iterator()) { spdlog::debug(
spdlog::debug("Archive has single entry which is a directory"); "Could extract all provided <paths>. But also <out> looks like an "
if(std::filesystem::equivalent(first_path.filename(), out.filename())) { "archive. Ambiguous intent. Assume this is not an extraction.");
spdlog::debug("Archive entry named like archive"); return make_extract_intent(userOpt);
int i = rand_int(0, 100000); }
path tmp_out = path{out}; unique_ptr<UserIntent> make_intent(const UserOpt &userOpt) {
tmp_out.concat(fmt::format(".xwim{}", i)); if (userOpt.wants_compress() && userOpt.wants_extract()) {
throw XwimError("Cannot compress and extract simultaneously");
}
if (userOpt.paths.empty()) {
throw XwimError("No input given...");
}
spdlog::debug("Moving {} to {}", first_path, tmp_out); // explicitly specified intent
std::filesystem::rename(first_path, tmp_out); if (userOpt.wants_compress()) return make_compress_intent(userOpt);
spdlog::debug("Removing parent {}", out); if (userOpt.wants_extract()) return make_extract_intent(userOpt);
std::filesystem::remove(out);
spdlog::debug("Moving {} to {}", tmp_out, out);
std::filesystem::rename(tmp_out, out);
} else { spdlog::info("Intent not explicitly provided, trying to infer intent");
spdlog::debug("Archive entry differs from archive name");
}
} else {
spdlog::debug("Archive has multiple entries");
}
}
}
};
void CompressSingleIntent::execute() { if (auto intent = try_infer_extract_intent(userOpt)) {
if(this->out.has_value()) { spdlog::info("Extraction intent inferred");
if(!can_handle_archive(this->out.value())) { return intent;
throw XwimError("Unknown archive format {}", this->out.value()); }
} spdlog::info("Cannot infer extraction intent");
unique_ptr<Archiver> archiver = make_archiver(this->out.value()); if (auto intent = try_infer_compress_intent(userOpt)) {
set<path> ins{this->in}; spdlog::info("Compression intent inferred");
archiver->compress(ins, this->out.value()); return intent;
} else { }
path out = default_archive(strip_archive_extension(this->in).stem()); spdlog::info("Cannot infer compression intent");
unique_ptr<Archiver> archiver = make_archiver(out);
set<path> ins{this->in};
archiver->compress(ins, out);
}
};
void CompressManyIntent::execute() { throw XwimError("Cannot guess intent");
if(!can_handle_archive(this->out)) { }
throw XwimError("Unknown archive format {}", this->out);
}
unique_ptr<Archiver> archiver = make_archiver(this->out); void ExtractIntent::dwim_reparent(const path &out) {
archiver->compress(this->in_paths, this->out); // move extraction if extraction resulted in only one entry and that entries
}; // name is already the stripped archive name, i.e. reduce unnecessary nesting
auto dit = std::filesystem::directory_iterator(out);
auto dit_path = dit->path();
if (dit == std::filesystem::directory_iterator()) {
spdlog::debug(
"Cannot flatten extraction folder: extraction folder is empty");
return;
}
if (!is_directory(dit_path)) {
spdlog::debug("Cannot flatten extraction folder: {} is not a directory",
dit_path);
return;
}
if (next(dit) != std::filesystem::directory_iterator()) {
spdlog::debug("Cannot flatten extraction folder: multiple items extracted");
return;
}
if (!std::filesystem::equivalent(dit_path.filename(), out.filename())) {
spdlog::debug(
"Cannot flatten extraction folder: archive entry differs from archive "
"name [extraction folder: {}, archive entry: {}]",
out.filename(), dit_path.filename());
return;
}
spdlog::debug("Output folder [{}] is equivalent to archive entry [{}]", out,
dit_path);
spdlog::info("Flattening extraction folder");
int i = rand_int(0, 100000);
path tmp_out = path{out};
tmp_out.concat(fmt::format(".xwim{}", i));
spdlog::debug("Move {} to {}", dit_path, tmp_out);
std::filesystem::rename(dit_path, tmp_out);
spdlog::debug("Remove parent path {}", out);
std::filesystem::remove(out);
spdlog::debug("Moving {} to {}", tmp_out, out);
std::filesystem::rename(tmp_out, out);
}
path ExtractIntent::out_path(const path &p) {
if (!this->out.has_value()) {
// not out path given, create from archive name
path out = std::filesystem::current_path() / strip_archive_extension(p);
create_directories(out);
return out;
}
if (this->archives.size() == 1) {
// out given and only one archive to extract, just extract into `out`
create_directories(this->out.value());
return this->out.value();
}
// out given and multiple archives to extract, create subfolder
// for each archive
create_directories(this->out.value());
path out = this->out.value() / strip_archive_extension(p);
return out;
}
void ExtractIntent::execute() {
for (const path &p : this->archives) {
std::unique_ptr<Archiver> archiver = make_archiver(p);
path out = this->out_path(p);
archiver->extract(p, out);
this->dwim_reparent(out);
}
}
path CompressSingleIntent::out_path() {
if (this->out.has_value()) {
if (!can_handle_archive(this->out.value())) {
throw XwimError("Unknown archive format {}", this->out.value());
}
return this->out.value();
}
return default_archive(strip_archive_extension(this->in).stem());
}
void CompressSingleIntent::execute() {
path out = this->out_path();
unique_ptr<Archiver> archiver = make_archiver(out);
set<path> ins{this->in};
archiver->compress(ins, out);
};
void CompressManyIntent::execute() {
if (!can_handle_archive(this->out)) {
throw XwimError("Unknown archive format {}", this->out);
}
unique_ptr<Archiver> archiver = make_archiver(this->out);
archiver->compress(this->in_paths, this->out);
}
} // namespace xwim } // namespace xwim

View file

@ -30,11 +30,14 @@ private:
set<path> archives; set<path> archives;
optional<path> out; optional<path> out;
public: void dwim_reparent(const path& out);
ExtractIntent(set<path> archives, optional<path> out): archives(archives), out(out) {}; path out_path(const path& p);
~ExtractIntent() = default;
void execute(); public:
ExtractIntent(set<path> archives, optional<path> out): archives(archives), out(out) {};
~ExtractIntent() override = default;
void execute() override;
}; };
/** /**
@ -55,14 +58,15 @@ public:
class CompressSingleIntent : public UserIntent { class CompressSingleIntent : public UserIntent {
private: private:
path in; path in;
optional <path> out; optional<path> out;
path out_path();
public: public:
CompressSingleIntent(path in, optional <path> out) : UserIntent(), in(in), out(out) {}; CompressSingleIntent(path in, optional<path> out) : UserIntent(), in(in), out(out) {};
~CompressSingleIntent() override = default;
~CompressSingleIntent() = default; void execute() override;
void execute();
}; };
/** /**
@ -81,9 +85,9 @@ private:
public: public:
CompressManyIntent(set<path> in_paths, path out): UserIntent(), in_paths(in_paths), out(out) {}; CompressManyIntent(set<path> in_paths, path out): UserIntent(), in_paths(in_paths), out(out) {};
~CompressManyIntent() = default; ~CompressManyIntent() override = default;
void execute(); void execute() override;
}; };
} // namespace xwim } // namespace xwim

View file

@ -1,11 +1,6 @@
#include "UserOpt.hpp" #include "UserOpt.hpp"
#include <tclap/ArgException.h>
#include <tclap/CmdLine.h> #include <tclap/CmdLine.h>
#include <tclap/StdOutput.h>
#include <tclap/SwitchArg.h>
#include <tclap/UnlabeledMultiArg.h>
#include <tclap/ValueArg.h>
template <> template <>
struct TCLAP::ArgTraits<std::filesystem::path> { struct TCLAP::ArgTraits<std::filesystem::path> {
@ -33,6 +28,9 @@ UserOpt::UserOpt(int argc, char** argv) {
TCLAP::ValueArg<fs::path> arg_outfile TCLAP::ValueArg<fs::path> arg_outfile
{"o", "out", "Out <file-or-path>", false, fs::path{}, "A path on the filesystem", cmd}; {"o", "out", "Out <file-or-path>", false, fs::path{}, "A path on the filesystem", cmd};
TCLAP::MultiSwitchArg arg_verbose
{"v", "verbose", "Verbosity level", cmd, 0};
TCLAP::UnlabeledMultiArg<fs::path> arg_paths TCLAP::UnlabeledMultiArg<fs::path> arg_paths
{"files", "Archive(s) to extract or file(s) to compress", true, "A path on the filesystem", cmd}; {"files", "Archive(s) to extract or file(s) to compress", true, "A path on the filesystem", cmd};
// clang-format on // clang-format on
@ -43,7 +41,8 @@ UserOpt::UserOpt(int argc, char** argv) {
if (arg_extract.isSet()) this->extract = arg_extract.getValue(); if (arg_extract.isSet()) this->extract = arg_extract.getValue();
if (arg_outfile.isSet()) this->out = arg_outfile.getValue(); if (arg_outfile.isSet()) this->out = arg_outfile.getValue();
this->interactive = arg_extract.getValue(); this->verbosity = arg_verbose.getValue();
this->interactive = !arg_noninteractive.getValue();
if (arg_paths.isSet()) { if (arg_paths.isSet()) {
this->paths = this->paths =

View file

@ -13,6 +13,7 @@ struct UserOpt {
optional<bool> compress; optional<bool> compress;
optional<bool> extract; optional<bool> extract;
bool interactive; bool interactive;
int verbosity;
std::optional<fs::path> out; std::optional<fs::path> out;
std::set<fs::path> paths; std::set<fs::path> paths;

View file

@ -1,13 +1,11 @@
#include <spdlog/common.h> #include <spdlog/common.h>
#include <spdlog/logger.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <cstdlib> #include <cstdlib>
#include <filesystem> #include <filesystem>
#include <optional>
#include "UserOpt.hpp"
#include "UserIntent.hpp" #include "UserIntent.hpp"
#include "UserOpt.hpp"
#include "util/Common.hpp" #include "util/Common.hpp"
#include "util/Log.hpp" #include "util/Log.hpp"
@ -16,12 +14,13 @@ using namespace std;
int main(int argc, char** argv) { int main(int argc, char** argv) {
log::init(); log::init();
UserOpt user_opt = UserOpt{argc, argv}; UserOpt user_opt = UserOpt{argc, argv};
log::init(user_opt.verbosity);
try { try {
unique_ptr<UserIntent> user_intent = make_intent(user_opt); unique_ptr<UserIntent> user_intent = make_intent(user_opt);
user_intent->execute(); user_intent->execute();
} catch(XwimError& e) { } catch (XwimError& e) {
spdlog::error(e.what()); spdlog::error(e.what());
} }
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <spdlog/common.h> #include <spdlog/common.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <cstdlib> #include <cstdlib>
#ifdef NDEBUG #ifdef NDEBUG
#define XWIM_LOGLEVEL SPDLOG_LEVEL_ERROR #define XWIM_LOGLEVEL SPDLOG_LEVEL_ERROR
@ -57,7 +58,27 @@ spdlog::level::level_enum _init_from_compile() {
* The determined level is then set for the default logger via * The determined level is then set for the default logger via
* `spdlog::set_level`. * `spdlog::set_level`.
*/ */
void init(spdlog::level::level_enum level = spdlog::level::level_enum::off) { void init(int verbosity = -1,
spdlog::level::level_enum level = spdlog::level::level_enum::off) {
if (verbosity != -1) {
switch (verbosity) {
case 0:
spdlog::set_level(spdlog::level::off);
break;
case 1:
spdlog::set_level(spdlog::level::info);
break;
case 2:
spdlog::set_level(spdlog::level::debug);
break;
case 3:
default:
spdlog::set_level(spdlog::level::trace);
break;
}
return;
}
if (spdlog::level::level_enum::off != level) { if (spdlog::level::level_enum::off != level) {
spdlog::set_level(level); spdlog::set_level(level);
return; return;