Add argument parser
All checks were successful
continuous-integration/drone/push Build is passing

- Provides better handling of invalid invocation
- Provides usage output
- More flexible for future extension (e.g. compression)
This commit is contained in:
Armin Friedl 2020-08-01 20:06:54 +02:00
parent 45a5d3372d
commit 7058c326c6
Signed by: armin
GPG key ID: 48C726EEE7FBCBC8
7 changed files with 179 additions and 6 deletions

View file

@ -4,6 +4,7 @@
#pragma once #pragma once
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <optional>
namespace logger = spdlog; namespace logger = spdlog;
#include <filesystem> #include <filesystem>
@ -22,7 +23,7 @@ namespace xwim {
*/ */
const std::set<std::string> fileformats{".7z", ".7zip", ".jar", ".tgz", const std::set<std::string> fileformats{".7z", ".7zip", ".jar", ".tgz",
".bz2", ".bzip2", ".gz", ".gzip", ".bz2", ".bzip2", ".gz", ".gzip",
".rar", ".tar", "xz", ".zip"}; ".rar", ".tar", ".xz", ".zip"};
/** Strip archive extensions from a path /** Strip archive extensions from a path
* *
@ -43,4 +44,26 @@ inline std::filesystem::path stem(const std::filesystem::path& path) {
return p_stem; return p_stem;
} }
/** Get the archive extension of a path.
*
* The archive extension may be a combination of supported fileformats in which
* case all of them are returned.
*
* @returns Archive extension of the archive or path() if no (known) extension
* exists.
*/
inline std::filesystem::path ext(const std::filesystem::path& path) {
std::filesystem::path p_ext{path};
logger::trace("Extracting extension of {}", p_ext.string());
std::filesystem::path p_ext_collector;
while (fileformats.find(p_ext.extension().string()) != fileformats.end()) {
// path extension() const
p_ext_collector = p_ext.extension().concat(p_ext_collector.string());
p_ext.replace_extension();
}
return p_ext_collector;
}
} // namespace xwim } // namespace xwim

View file

@ -1,4 +1,5 @@
#include <spdlog/common.h> #include <spdlog/common.h>
#include <cstdlib>
namespace logger = spdlog; namespace logger = spdlog;
#include <iostream> #include <iostream>
@ -7,6 +8,7 @@ namespace logger = spdlog;
#include <list> #include <list>
#include "util/log.hpp" #include "util/log.hpp"
#include "util/argparse.hpp"
#include "archive.hpp" #include "archive.hpp"
#include "spec.hpp" #include "spec.hpp"
#include "fileformats.hpp" #include "fileformats.hpp"
@ -14,9 +16,18 @@ namespace logger = spdlog;
int main(int argc, char** argv) { int main(int argc, char** argv) {
xwim::log::init(); xwim::log::init();
xwim::argparse::XwimPath xwim_path;
try { try {
std::filesystem::path filepath{argv[1]}; xwim_path = xwim::argparse::parse(argc, argv);
xwim::Archive archive{filepath}; } catch (xwim::argparse::ArgParseException& ex) {
logger::error("{}\n", ex.what());
std::cout << xwim::argparse::usage();
std::exit(1);
}
try {
xwim::Archive archive{xwim_path.path()};
xwim::ArchiveSpec archive_spec = archive.check(); xwim::ArchiveSpec archive_spec = archive.check();
logger::info("{}", archive_spec); logger::info("{}", archive_spec);
@ -26,7 +37,7 @@ int main(int argc, char** argv) {
if (!archive_spec.has_single_root || !archive_spec.is_root_filename) { if (!archive_spec.has_single_root || !archive_spec.is_root_filename) {
extract_spec.make_dir = true; extract_spec.make_dir = true;
std::filesystem::path stem = xwim::stem(filepath); std::filesystem::path stem = xwim::stem(xwim_path.path());
extract_spec.dirname = stem; extract_spec.dirname = stem;
} }

View file

@ -1,6 +1,7 @@
xwim_src = ['main.cpp', xwim_src = ['main.cpp',
'archive.cpp', 'archive.cpp',
'archive_sys.cpp'] 'archive_sys.cpp',
'util/argparse.cpp']
xwim_libs = [dependency('libarchive', required: true), xwim_libs = [dependency('libarchive', required: true),
dependency('fmt', required: true), dependency('fmt', required: true),

29
src/util/argparse.cpp Normal file
View file

@ -0,0 +1,29 @@
#include "argparse.hpp"
namespace xwim {
namespace argparse {
XwimPath parse(int argc, char** argv) {
return XwimPath{argc, argv};
}
// contructs XwimPath{} first so that destructurs may running
// http://www.vishalchovatiya.com/7-best-practices-for-exception-handling-in-cpp-with-example/
XwimPath::XwimPath(int argc, char** argv) : XwimPath{} {
if (argc < 2) throw ArgParseException{"No argument provided"};
if (argc > 2) throw ArgParseException{"Too many arguments provided"};
this->_path = std::filesystem::path{argv[1]};
// Remove when compression in place
if(!is_archive()) throw ArgParseException{"Not a known archive format"};
}
bool XwimPath::is_archive() {
return !xwim::ext(_path).empty();
}
std::filesystem::path XwimPath::path() const {
return std::filesystem::path{_path};
}
}
}

79
src/util/argparse.hpp Normal file
View file

@ -0,0 +1,79 @@
#pragma once
#include <filesystem>
#include <ostream>
#include <sstream>
#include "../fileformats.hpp"
namespace xwim {
/**
* xwim allows for
* 1. an archive
* 2. a file or folder
*
* In case of (1) the archive will be extracted according to the xwim
* do-what-i-mean rules.
*
* In case of (2) the file or folder will be compressed into a "platform native"
* format, i.e. what appears to be the most widely used format on that platform.
* In case of unix this is tar.gz. In case of windows this is zip. The archive
* gets the same name as the file or folder and a proper extension.
*
* A list of files or folders is unsupported as it would be too ambigious to
* choose a name for the archive. A list of archives is unsupported for
* consistency reasons. Any mixture is unsupported as it would be too ambigious
* what the user wants. This is subject to change in the future.
*/
namespace argparse {
class XwimPath {
private:
std::filesystem::path _path;
public:
XwimPath() : _path{} {};
XwimPath(int argc, char** argv);
bool is_archive();
std::filesystem::path path() const;
};
class ArgParseException : public std::exception {
private:
std::string _what;
public:
ArgParseException(std::string what) : _what{what} {};
virtual const char* what() const noexcept { return this->_what.c_str(); }
};
XwimPath parse(int argc, char** argv);
inline std::string usage() {
std::stringstream s;
s << "USAGE:"
<< "\t xwim <path>\n"
<< "\n"
<< "PARAMS:" << std::left << std::setfill('.') << std::setw(10)
<< "\t path "
<< " Archive\n"
<< "\n"
<< "FORMATS:\n"
<< "\t .7z, .7zip .jar, .tgz, .bz2, .bzip2\n"
<< "\t .gz, .gzip, .rar, .tar, .xz, .zip\n"
<< "\n"
<< "EXAMPLES:\n"
<< "\t Extract archive archive.tar.gz:\n"
<< "\t xwim archive.tar.gz\n"
<< std::endl;
return s.str();
}
} // namespace argparse
} // namespace xwim

View file

@ -34,7 +34,7 @@ spdlog::level::level_enum _init_from_env() {
} }
return lvl; return lvl;
}; }
/** /**
* Get log level from compile time definition. * Get log level from compile time definition.

View file

@ -32,3 +32,33 @@ TEST(FileformatsTest, StemStripsNothingWithoutExtension) {
ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"filerar"}); ASSERT_EQ(xwim::stem(archive_path), std::filesystem::path{"filerar"});
} }
TEST(FileExtTest, ExtGetsKnownExtension) {
std::filesystem::path archive_path{"/some/path/to/file.rar"};
ASSERT_EQ(xwim::ext(archive_path), std::filesystem::path{".rar"});
}
TEST(FileExtTest, CombinedExtensionGetsAll) {
std::filesystem::path archive_path{"/some/path/to/file.tar.gz"};
ASSERT_EQ(xwim::ext(archive_path), std::filesystem::path{".tar.gz"});
}
TEST(FileExtTest, ExtEmptyForUnknownExtension) {
std::filesystem::path archive_path{"/some/path/to/file.ukn"};
ASSERT_TRUE(xwim::ext(archive_path).empty());
}
TEST(FileExtTest, CombinedExtensionGetsKnown) {
std::filesystem::path archive_path{"/some/path/to/file.ukn.tar.gz"};
ASSERT_EQ(xwim::ext(archive_path), std::filesystem::path{".tar.gz"});
}
TEST(FileExtTest, CombinedExtensionLastUnknownEmpty) {
std::filesystem::path archive_path{"/some/path/to/file.tar.gz.ukn"};
ASSERT_TRUE(xwim::ext(archive_path).empty());
}