Restructure to util add split Intent and Options
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Armin Friedl 2022-04-09 18:23:18 +02:00
parent 89dd5186f5
commit 70ae623a1d
Signed by: armin
GPG key ID: 48C726EEE7FBCBC8
17 changed files with 405 additions and 87 deletions

View file

@ -5,7 +5,9 @@ project('xwim', 'cpp',
'b_ndebug=if-release']) 'b_ndebug=if-release'])
add_global_arguments('-DVERSION='+meson.version(), language: 'cpp') add_global_arguments('-DVERSION='+meson.version(), language: 'cpp')
add_global_arguments('-DSPDLOG_FMT_EXTERNAL', language: 'cpp')
add_global_arguments('-DFMT_HEADER_ONLY', language: 'cpp')
subdir('src') subdir('src')
subdir('doc') subdir('doc')
# subdir('test') subdir('test')

View file

@ -7,7 +7,7 @@
#include <map> #include <map>
#include <memory> #include <memory>
#include "Common.hpp" #include "util/Common.hpp"
namespace xwim { namespace xwim {
using namespace std; using namespace std;

View file

@ -7,7 +7,7 @@
#include <memory> #include <memory>
#include <set> #include <set>
#include "Common.hpp" #include "util/Common.hpp"
namespace xwim { namespace xwim {

52
src/UserOpt.cpp Normal file
View file

@ -0,0 +1,52 @@
#include "UserOpt.hpp"
#include <tclap/ArgException.h>
#include <tclap/CmdLine.h>
#include <tclap/StdOutput.h>
#include <tclap/SwitchArg.h>
#include <tclap/UnlabeledMultiArg.h>
#include <tclap/ValueArg.h>
template <>
struct TCLAP::ArgTraits<std::filesystem::path> {
// We use `operator=` here for path construction
// because `operator>>` (`ValueLike`) causes a split at
// whitespace
typedef StringLike ValueCategory;
};
namespace xwim {
UserOpt::UserOpt(int argc, char** argv) {
// clang-format off
TCLAP::CmdLine cmd
{"xwim - Do What I Mean Extractor", ' ', "0.3.0"};
TCLAP::SwitchArg arg_compress
{"c", "compress", "Compress <files>", cmd, false};
TCLAP::SwitchArg arg_extract
{"x", "extract", "Extract <file>", cmd, false};
TCLAP::SwitchArg arg_noninteractive
{"i", "non-interactive", "Non-interactive, fail on ambiguity", cmd, false};
TCLAP::ValueArg<fs::path> arg_outfile
{"o", "out", "Out <file-or-path>", false, fs::path{}, "A path on the filesystem", cmd};
TCLAP::UnlabeledMultiArg<fs::path> arg_paths
{"files", "Archive to extract or files to compress", true, "A path on the filesystem", cmd};
// clang-format on
cmd.parse(argc, argv);
this->compress = arg_compress.getValue();
this->extract = arg_extract.getValue();
this->interactive = arg_extract.getValue();
if (arg_outfile.isSet()) this->out = arg_outfile.getValue();
if (arg_paths.isSet()) {
this->paths =
set<fs::path>{arg_paths.getValue().begin(), arg_paths.getValue().end()};
}
}
} // namespace xwim

22
src/UserOpt.hpp Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <optional>
#include <set>
#include "util/Common.hpp"
namespace xwim {
using namespace std;
namespace fs = std::filesystem;
struct UserOpt {
bool compress;
bool extract;
bool interactive;
std::optional<fs::path> out;
std::set<fs::path> paths;
UserOpt(int argc, char** argv);
};
} // namespace xwim

View file

@ -12,7 +12,7 @@
#include <string> #include <string>
#include "Archiver.hpp" #include "Archiver.hpp"
#include "Common.hpp" #include "util/Common.hpp"
namespace xwim { namespace xwim {
using namespace std; using namespace std;

View file

@ -5,17 +5,61 @@
#include <exception> #include <exception>
#include <memory> #include <memory>
#include <optional>
#include <set> #include <set>
#include <stdexcept> #include <stdexcept>
#include "Common.hpp"
#include "Archiver.hpp" #include "Archiver.hpp"
#include "util/Common.hpp"
#include "UserOpt.hpp"
namespace xwim { namespace xwim {
using namespace std; using namespace std;
namespace fs = std::filesystem; namespace fs = std::filesystem;
enum class Action { UNKNOWN, EXTRACT, COMPRESS }; enum class Action { EXTRACT, COMPRESS };
struct XwimIntent {
};
class XwimBuilder {
private:
UserOpt user_opt;
public:
XwimBuilder(UserOpt user_opt) : user_opt(user_opt){};
Xwim build();
};
class Xwim {
public:
virtual XwimResult dwim() = 0;
};
class XwimCompressor : public Xwim {
private:
fs::path archive;
std::set<fs::path> paths;
};
class XwimExtractor : public Xwim {};
class XwimConfig {
public:
Action get_action();
}
class Xwim {
private:
XwimEngine xwim_engine;
UserOpt user_opt;
public:
Xwim(UserOpt user_opt);
void dwim();
}
class Xwim { class Xwim {
private: private:

15
src/XwimConfig.hpp Normal file
View file

@ -0,0 +1,15 @@
#pragma once
namespace xwim {
enum class Action { COMPRESS, EXTRACT };
class XwimConfig {
public:
Action get_action();
}
}

122
src/XwimIntent.cpp Normal file
View file

@ -0,0 +1,122 @@
#include "XwimIntent.hpp"
#include <spdlog/spdlog.h>
#include <tclap/ArgException.h>
#include <tclap/CmdLine.h>
#include <tclap/StdOutput.h>
#include <tclap/SwitchArg.h>
#include <tclap/UnlabeledMultiArg.h>
#include <tclap/ValueArg.h>
#include <algorithm>
#include <exception>
#include <filesystem>
#include <iostream>
#include <optional>
#include "Archiver.hpp"
template <>
struct::TCLAP::ArgTraits<std::filesystem::path> {
// `operator=` here for path construction because `operator>>`
// (`ValueLike`) causes a split at whitespace
typedef StringLike ValueCategory;
};
namespace xwim {
void UserOpt::parse_args(int argc, char** argv) {
// clang-format off
// TODO: read version from -DVERSION during compilation
TCLAP::CmdLine cmd {"xwim - Do What I Mean Extractor", ' ', "0.3.0"};
TCLAP::SwitchArg arg_compress
{"c", "compress", "Compress <files>", cmd, false};
TCLAP::SwitchArg arg_extract
{"x", "extract", "Extract <file>", cmd, false};
TCLAP::SwitchArg arg_noninteractive
{"i", "non-interactive", "Non-interactive, fail on ambiguity", cmd, false};
TCLAP::ValueArg<path> arg_outfile
{"o", "out", "Out <file-or-path>", false, path{}, "A path on the filesystem", cmd};
TCLAP::UnlabeledMultiArg<path> arg_paths
{"files", "Archive to extract or files to compress", true, "A path on the filesystem", cmd};
// clang-format on
// TODO: ideally we'd make sure during parsing that compress and extract
// cannot both be true
cmd.parse(argc, argv);
// clang-format off
// Only set things if they are actually parsed from args. Otherwise we'd
// override settings set through other means, e.g. config files
if (arg_compress.isSet()) { this->compress = arg_compress.getValue(); }
if (arg_extract.isSet()) { this->extract = arg_extract.getValue(); }
if (arg_noninteractive.isSet()) { this->interactive = !arg_noninteractive.getValue(); }
if (arg_outfile.isSet()) { this->out = arg_outfile.getValue(); }
if (arg_paths.isSet()) { this->paths = arg_paths.getValue(); }
// clang-format on
}
void UserOpt::parse_config(path config) { // TODO
spdlog::warn("Config parsing is not implemented");
return;
}
UserIntent UserOpt::guess_intent() {
return UserIntent{action_intent(), out_intent(), paths_intent()};
}
Action UserOpt::action_intent() {
if (compress && extract) {
throw XwimError("Cannot compress and extract simultaneously");
}
if (compress) return Action::COMPRESS;
if (extract) return Action::EXTRACT;
bool can_extract_all = std::all_of(
paths.begin(), paths.end(), [](path path) { return can_extract(path); });
if (can_extract_all && !out) {
return Action::EXTRACT;
} // else if can_extract_all && !is_archive(out) -> EXTRACT
if (!can_extract_all && out /* && is_archive(out) */) {
return Action::COMPRESS;
}
if (interactive) {
std::cout << "Do you want to compress (y/n)? [y] ";
char c;
std::cin >> c;
if (c != 'y' && c != 'n' && c != '\n') {
throw XwimError("Cannot guess action. Please answer 'y' or 'n'.");
}
if (c == 'y' || c == '\n') {
return Action::COMPRESS;
} else if (c == 'n') {
return Action::EXTRACT;
}
}
throw XwimError("Cannot guess action (compress/extract)");
}
path UserOpt::out_intent() {
}
set<path> UserOpt::paths_intent() {
}
} // namespace xwim

38
src/XwimIntent.hpp Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include <optional>
#include <set>
#include "util/Common.hpp"
namespace xwim {
using namespace std;
using std::filesystem::path;
enum class Action { COMPRESS, EXTRACT };
struct UserIntent {
Action action;
path out;
set<path> paths;
};
class UserOpt {
private:
bool compress = true;
bool extract = false;
bool interactive = true;
optional<path> out = nullopt;
vector<path> paths = std::vector<path>{};
Action action_intent();
path out_intent();
set<path> paths_intent();
public:
void parse_config(path config);
void parse_args(int argc, char** argv);
UserIntent guess_intent();
};
} // namespace xwim

View file

@ -9,8 +9,8 @@
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include "Archiver.hpp" #include "../Archiver.hpp"
#include "Common.hpp" #include "../util/Common.hpp"
namespace xwim { namespace xwim {
using namespace std; using namespace std;

View file

@ -1,75 +1,20 @@
#include <spdlog/common.h> #include <spdlog/common.h>
#include <spdlog/logger.h> #include <spdlog/logger.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <tclap/ArgException.h>
#include <tclap/CmdLine.h>
#include <tclap/StdOutput.h>
#include <tclap/SwitchArg.h>
#include <tclap/UnlabeledMultiArg.h>
#include <tclap/ValueArg.h>
#include <cstdlib> #include <cstdlib>
#include <filesystem> #include <filesystem>
#include <optional>
#include "Common.hpp" #include "UserOpt.hpp"
#include "Log.hpp" #include "util/Common.hpp"
#include "Xwim.hpp" #include "util/Log.hpp"
using namespace xwim; using namespace xwim;
using namespace std; using namespace std;
namespace fs = std::filesystem;
template <>
struct TCLAP::ArgTraits<std::filesystem::path> {
typedef ValueLike ValueCategory;
};
int main(int argc, char** argv) { int main(int argc, char** argv) {
log::init(); log::init();
TCLAP::CmdLine cmd{"xwim - Do What I Mean Extractor", ' ', "0.3.0"}; UserOpt user_opt = UserOpt{argc, argv};
TCLAP::SwitchArg arg_compress{"c", "compress", "Compress <files>", cmd,
false};
TCLAP::SwitchArg arg_extract{"x", "extract", "Extract <file>", cmd, false};
TCLAP::ValueArg<fs::path> arg_outfile{
"o", "out", "Out <file-or-path>",
false, fs::path{}, "A path on the filesystem",
cmd};
TCLAP::UnlabeledMultiArg<fs::path> arg_infiles{
"Files", "Archive to extract or files to compress", true,
"A path on the filesystem", cmd};
Xwim xwim;
cmd.parse(argc, argv);
if (arg_extract.isSet() && arg_compress.isSet()) {
// This is a bit ugly but `none-or-xor` only available in
// tclap-1.4 which is not well supported in current
// distributions
auto out = TCLAP::StdOutput{};
TCLAP::ArgException e{
"Cannot compress `-c` and extract `-x` simultaneously"};
try {
out.failure(cmd, e);
} catch (TCLAP::ExitException& e) {
exit(e.getExitStatus());
}
}
// `none-or-xor` ensured already
if (arg_extract.isSet()) xwim.setExtract();
if (arg_compress.isSet()) xwim.setCompress();
if (arg_outfile.isSet()) xwim.setOut(arg_outfile.getValue());
if (arg_infiles.isSet()) xwim.setIns(arg_infiles.getValue());
try {
xwim.try_infer();
xwim.dwim();
} catch (XwimError& e) {
spdlog::error(e.what());
}
} }

View file

@ -1,11 +1,11 @@
xwim_src = ['main.cpp', 'Xwim.cpp', 'Archiver.cpp'] xwim_src = ['main.cpp', 'Xwim.cpp', 'Archiver.cpp', 'UserOpt.cpp']
xwim_archiver = ['archiver/LibArchiver.cpp'] xwim_archiver = ['archiver/LibArchiver.cpp']
is_static = get_option('default_library')=='static' is_static = get_option('default_library')=='static'
xwim_libs = [dependency('libarchive', required: true, static: is_static), xwim_libs = [dependency('libarchive', required: true, static: is_static),
dependency('fmt', required: true, static: is_static),
dependency('spdlog', required: true, static: is_static), dependency('spdlog', required: true, static: is_static),
dependency('fmt', required: true, static: is_static),
dependency('tclap', required: true, static: is_static)] dependency('tclap', required: true, static: is_static)]
executable('xwim', xwim_src+xwim_archiver, dependencies: xwim_libs) executable('xwim', xwim_src+xwim_archiver, dependencies: xwim_libs)

View file

@ -2,22 +2,10 @@
gtest_proj = subproject('gtest') gtest_proj = subproject('gtest')
gtest_dep = gtest_proj.get_variable('gtest_main_dep') gtest_dep = gtest_proj.get_variable('gtest_main_dep')
xwim_src = ['../src/archive.cpp', # subdir('archives')
'../src/archive_sys.cpp'] user_opt_test_exe = executable('user_opt_test_exe',
sources: ['user_opt_test.cpp', '../src/UserOpt.cpp'],
subdir('archives')
archive_test_exe = executable('archive_test_exe',
sources: ['archive_test.cpp', xwim_src],
include_directories: ['../src'], include_directories: ['../src'],
dependencies: [gtest_dep, xwim_libs]) dependencies: [gtest_dep])
test('user opt parsing test', user_opt_test_exe)
test('archive test', archive_test_exe)
fileformats_test_exe = executable('fileformats_test_exe',
sources: ['fileformats_test.cpp', xwim_src],
include_directories: ['../src'],
dependencies: [gtest_dep, xwim_libs])
test('fileformats test', fileformats_test_exe)

90
test/user_opt_test.cpp Normal file
View file

@ -0,0 +1,90 @@
#include <gtest/gtest-death-test.h>
#include "gtest/gtest.h"
#include <filesystem>
#include <string>
#include "UserOpt.hpp"
TEST(UserOpt, compress) {
using namespace xwim;
// clang-format off
char* args[] = {
const_cast<char*>("xwim"),
const_cast<char*>("-c"),
const_cast<char*>("mandator_paths"),
nullptr};
// clang-format on
UserOpt uo = UserOpt{3, args};
ASSERT_TRUE(uo.compress);
ASSERT_FALSE(uo.extract);
}
TEST(UserOpt, exclusive_actions) {
using namespace xwim;
// clang-format off
char* args[] = {
const_cast<char*>("xwim"),
const_cast<char*>("-c"),
const_cast<char*>("-x"),
const_cast<char*>("mandatory_paths"),
nullptr};
// clang-format on
UserOpt uo = UserOpt{4, args};
ASSERT_TRUE(uo.compress);
ASSERT_TRUE(uo.extract);
}
TEST(UserOpt, whitespace_in_path) {
using namespace xwim;
// clang-format off
char* args[] = {
const_cast<char*>("xwim"),
const_cast<char*>("-c"),
const_cast<char*>("/foo/bar baz/a file"),
nullptr};
// clang-format on
UserOpt uo = UserOpt{3, args};
ASSERT_TRUE(uo.paths.find(std::filesystem::path("/foo/bar baz/a file")) !=
uo.paths.end());
}
TEST(UserOpt, mixed_output_and_paths) {
using namespace xwim;
// clang-format off
char* args[] = {
const_cast<char*>("xwim"),
const_cast<char*>("-o"),
const_cast<char*>("/foo/bar baz/output"),
const_cast<char*>("/foo/bar baz/a path"),
const_cast<char*>("/foo/bar baz/another path"),
nullptr};
// clang-format on
UserOpt uo = UserOpt{5, args};
ASSERT_TRUE(uo.paths.find(std::filesystem::path("/foo/bar baz/a path")) !=
uo.paths.end());
ASSERT_TRUE(uo.paths.find(std::filesystem::path("/foo/bar baz/another path")) !=
uo.paths.end());
ASSERT_TRUE(uo.out == std::filesystem::path("/foo/bar baz/output"));
}
TEST(UserOpt, output_defaults_to_nullopt) {
using namespace xwim;
// clang-format off
char* args[] = {
const_cast<char*>("xwim"),
const_cast<char*>("/foo/bar"),
nullptr};
// clang-format on
UserOpt uo = UserOpt{2, args};
ASSERT_FALSE(uo.out);
}