From 70ae623a1d081ccf166e3534ec1868eafb3fc65d Mon Sep 17 00:00:00 2001 From: Armin Friedl Date: Sat, 9 Apr 2022 18:23:18 +0200 Subject: [PATCH] Restructure to `util` add split Intent and Options --- meson.build | 4 +- src/Archiver.cpp | 2 +- src/Archiver.hpp | 2 +- src/UserOpt.cpp | 52 +++++++++++++++ src/UserOpt.hpp | 22 +++++++ src/Xwim.cpp | 2 +- src/Xwim.hpp | 48 +++++++++++++- src/XwimConfig.hpp | 15 +++++ src/XwimIntent.cpp | 122 +++++++++++++++++++++++++++++++++++ src/XwimIntent.hpp | 38 +++++++++++ src/archiver/LibArchiver.cpp | 4 +- src/main.cpp | 65 ++----------------- src/meson.build | 4 +- src/{ => util}/Common.hpp | 0 src/{ => util}/Log.hpp | 0 test/meson.build | 22 ++----- test/user_opt_test.cpp | 90 ++++++++++++++++++++++++++ 17 files changed, 405 insertions(+), 87 deletions(-) create mode 100644 src/UserOpt.cpp create mode 100644 src/UserOpt.hpp create mode 100644 src/XwimConfig.hpp create mode 100644 src/XwimIntent.cpp create mode 100644 src/XwimIntent.hpp rename src/{ => util}/Common.hpp (100%) rename src/{ => util}/Log.hpp (100%) create mode 100644 test/user_opt_test.cpp diff --git a/meson.build b/meson.build index d6b8891..3361155 100644 --- a/meson.build +++ b/meson.build @@ -5,7 +5,9 @@ project('xwim', 'cpp', 'b_ndebug=if-release']) 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('doc') -# subdir('test') +subdir('test') diff --git a/src/Archiver.cpp b/src/Archiver.cpp index 018a0b3..a29960d 100644 --- a/src/Archiver.cpp +++ b/src/Archiver.cpp @@ -7,7 +7,7 @@ #include #include -#include "Common.hpp" +#include "util/Common.hpp" namespace xwim { using namespace std; diff --git a/src/Archiver.hpp b/src/Archiver.hpp index 52af90e..9d07848 100644 --- a/src/Archiver.hpp +++ b/src/Archiver.hpp @@ -7,7 +7,7 @@ #include #include -#include "Common.hpp" +#include "util/Common.hpp" namespace xwim { diff --git a/src/UserOpt.cpp b/src/UserOpt.cpp new file mode 100644 index 0000000..8d57261 --- /dev/null +++ b/src/UserOpt.cpp @@ -0,0 +1,52 @@ +#include "UserOpt.hpp" + +#include +#include +#include +#include +#include +#include + +template <> +struct TCLAP::ArgTraits { + // 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 ", cmd, false}; + + TCLAP::SwitchArg arg_extract + {"x", "extract", "Extract ", cmd, false}; + + TCLAP::SwitchArg arg_noninteractive + {"i", "non-interactive", "Non-interactive, fail on ambiguity", cmd, false}; + + TCLAP::ValueArg arg_outfile + {"o", "out", "Out ", false, fs::path{}, "A path on the filesystem", cmd}; + + TCLAP::UnlabeledMultiArg 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{arg_paths.getValue().begin(), arg_paths.getValue().end()}; + } +} +} // namespace xwim diff --git a/src/UserOpt.hpp b/src/UserOpt.hpp new file mode 100644 index 0000000..a1058fc --- /dev/null +++ b/src/UserOpt.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +#include "util/Common.hpp" + +namespace xwim { +using namespace std; +namespace fs = std::filesystem; + +struct UserOpt { + bool compress; + bool extract; + bool interactive; + std::optional out; + std::set paths; + + UserOpt(int argc, char** argv); +}; + +} // namespace xwim diff --git a/src/Xwim.cpp b/src/Xwim.cpp index ab3160c..001b610 100644 --- a/src/Xwim.cpp +++ b/src/Xwim.cpp @@ -12,7 +12,7 @@ #include #include "Archiver.hpp" -#include "Common.hpp" +#include "util/Common.hpp" namespace xwim { using namespace std; diff --git a/src/Xwim.hpp b/src/Xwim.hpp index 6741bb7..3cc9bdb 100644 --- a/src/Xwim.hpp +++ b/src/Xwim.hpp @@ -5,17 +5,61 @@ #include #include +#include #include #include -#include "Common.hpp" #include "Archiver.hpp" +#include "util/Common.hpp" +#include "UserOpt.hpp" namespace xwim { using namespace std; 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 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 { private: diff --git a/src/XwimConfig.hpp b/src/XwimConfig.hpp new file mode 100644 index 0000000..8ae6e9b --- /dev/null +++ b/src/XwimConfig.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace xwim { + +enum class Action { COMPRESS, EXTRACT }; + +class XwimConfig { + +public: + Action get_action(); + + +} + +} diff --git a/src/XwimIntent.cpp b/src/XwimIntent.cpp new file mode 100644 index 0000000..7e05539 --- /dev/null +++ b/src/XwimIntent.cpp @@ -0,0 +1,122 @@ +#include "XwimIntent.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "Archiver.hpp" + +template <> +struct::TCLAP::ArgTraits { + // `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 ", cmd, false}; + + TCLAP::SwitchArg arg_extract + {"x", "extract", "Extract ", cmd, false}; + + TCLAP::SwitchArg arg_noninteractive + {"i", "non-interactive", "Non-interactive, fail on ambiguity", cmd, false}; + + TCLAP::ValueArg arg_outfile + {"o", "out", "Out ", false, path{}, "A path on the filesystem", cmd}; + + TCLAP::UnlabeledMultiArg 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 UserOpt::paths_intent() { + +} + +} // namespace xwim diff --git a/src/XwimIntent.hpp b/src/XwimIntent.hpp new file mode 100644 index 0000000..bbd5a95 --- /dev/null +++ b/src/XwimIntent.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#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 paths; +}; + +class UserOpt { +private: + bool compress = true; + bool extract = false; + bool interactive = true; + optional out = nullopt; + vector paths = std::vector{}; + + Action action_intent(); + path out_intent(); + set paths_intent(); + + public: + void parse_config(path config); + void parse_args(int argc, char** argv); + + UserIntent guess_intent(); +}; + +} // namespace xwim diff --git a/src/archiver/LibArchiver.cpp b/src/archiver/LibArchiver.cpp index f448df6..ab0e012 100644 --- a/src/archiver/LibArchiver.cpp +++ b/src/archiver/LibArchiver.cpp @@ -9,8 +9,8 @@ #include #include -#include "Archiver.hpp" -#include "Common.hpp" +#include "../Archiver.hpp" +#include "../util/Common.hpp" namespace xwim { using namespace std; diff --git a/src/main.cpp b/src/main.cpp index 1a6ca8a..597622f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,75 +1,20 @@ #include #include #include -#include -#include -#include -#include -#include -#include #include #include +#include -#include "Common.hpp" -#include "Log.hpp" -#include "Xwim.hpp" +#include "UserOpt.hpp" +#include "util/Common.hpp" +#include "util/Log.hpp" using namespace xwim; using namespace std; -namespace fs = std::filesystem; - -template <> -struct TCLAP::ArgTraits { - typedef ValueLike ValueCategory; -}; int main(int argc, char** argv) { log::init(); - TCLAP::CmdLine cmd{"xwim - Do What I Mean Extractor", ' ', "0.3.0"}; - - TCLAP::SwitchArg arg_compress{"c", "compress", "Compress ", cmd, - false}; - TCLAP::SwitchArg arg_extract{"x", "extract", "Extract ", cmd, false}; - - TCLAP::ValueArg arg_outfile{ - "o", "out", "Out ", - false, fs::path{}, "A path on the filesystem", - cmd}; - TCLAP::UnlabeledMultiArg 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()); - } + UserOpt user_opt = UserOpt{argc, argv}; } diff --git a/src/meson.build b/src/meson.build index 1f6588c..a0cbead 100644 --- a/src/meson.build +++ b/src/meson.build @@ -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'] is_static = get_option('default_library')=='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('fmt', required: true, static: is_static), dependency('tclap', required: true, static: is_static)] executable('xwim', xwim_src+xwim_archiver, dependencies: xwim_libs) diff --git a/src/Common.hpp b/src/util/Common.hpp similarity index 100% rename from src/Common.hpp rename to src/util/Common.hpp diff --git a/src/Log.hpp b/src/util/Log.hpp similarity index 100% rename from src/Log.hpp rename to src/util/Log.hpp diff --git a/test/meson.build b/test/meson.build index e6f5101..c985375 100644 --- a/test/meson.build +++ b/test/meson.build @@ -2,22 +2,10 @@ gtest_proj = subproject('gtest') gtest_dep = gtest_proj.get_variable('gtest_main_dep') -xwim_src = ['../src/archive.cpp', - '../src/archive_sys.cpp'] - -subdir('archives') - -archive_test_exe = executable('archive_test_exe', - sources: ['archive_test.cpp', xwim_src], +# subdir('archives') +user_opt_test_exe = executable('user_opt_test_exe', + sources: ['user_opt_test.cpp', '../src/UserOpt.cpp'], include_directories: ['../src'], - dependencies: [gtest_dep, xwim_libs]) + dependencies: [gtest_dep]) - -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) +test('user opt parsing test', user_opt_test_exe) diff --git a/test/user_opt_test.cpp b/test/user_opt_test.cpp new file mode 100644 index 0000000..99e51b6 --- /dev/null +++ b/test/user_opt_test.cpp @@ -0,0 +1,90 @@ +#include +#include "gtest/gtest.h" +#include +#include + +#include "UserOpt.hpp" + +TEST(UserOpt, compress) { + using namespace xwim; + + // clang-format off + char* args[] = { + const_cast("xwim"), + const_cast("-c"), + const_cast("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("xwim"), + const_cast("-c"), + const_cast("-x"), + const_cast("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("xwim"), + const_cast("-c"), + const_cast("/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("xwim"), + const_cast("-o"), + const_cast("/foo/bar baz/output"), + const_cast("/foo/bar baz/a path"), + const_cast("/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("xwim"), + const_cast("/foo/bar"), + nullptr}; + // clang-format on + + UserOpt uo = UserOpt{2, args}; + ASSERT_FALSE(uo.out); +}