From 6fbc93e12e908cc0c9ec73bac70fce59b7092904 Mon Sep 17 00:00:00 2001 From: Ade Attwood Date: Sun, 4 Feb 2018 19:39:45 +0000 Subject: [PATCH] Initial commit --- .gitignore | 89 +++++++++++++++++++++++++++++++ .vscode/settings.json | 8 +++ .vscode/tasks.json | 57 ++++++++++++++++++++ CMakeLists.txt | 37 +++++++++++++ apt-pkg.txt | 6 +++ builder | 64 +++++++++++++++++++++++ man/man1/mktouch.1 | 36 +++++++++++++ src/App.cpp | 107 +++++++++++++++++++++++++++++++++++++ src/App.h | 39 ++++++++++++++ src/CMakeLists.txt | 0 src/Cli.cpp | 119 ++++++++++++++++++++++++++++++++++++++++++ src/Cli.h | 31 +++++++++++ src/main.cpp | 59 +++++++++++++++++++++ 13 files changed, 652 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 apt-pkg.txt create mode 100755 builder create mode 100644 man/man1/mktouch.1 create mode 100644 src/App.cpp create mode 100644 src/App.h create mode 100644 src/CMakeLists.txt create mode 100644 src/Cli.cpp create mode 100644 src/Cli.h create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64b1a9d --- /dev/null +++ b/.gitignore @@ -0,0 +1,89 @@ +# Created by https://www.gitignore.io/api/git,linux,macos,cmake,windows,visualstudiocode + +### CMake ### +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +build + +### Git ### +*.orig + +### 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 ### +*.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 + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/git,linux,macos,cmake,windows,visualstudiocode \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1529382 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "iostream": "cpp" + }, + "cSpell.words": [ + "stoi" + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..31dc53b --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,57 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build development", + "type": "shell", + "command": "./builder debug", + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "focus": true + } + }, + { + "label": "Build porduction", + "type": "shell", + "command": "./builder release", + "problemMatcher": [ + "$gcc" + ], + "presentation": { + "reveal": "always", + "focus": true + } + }, + { + "label": "Package", + "type": "shell", + "command": "./builder package", + "problemMatcher": [ + "$gcc" + ], + "presentation": { + "reveal": "always", + "focus": true + } + }, + { + "label": "Clean", + "type": "shell", + "command": "./builder clean", + "presentation": { + "reveal": "always", + "focus": true + }, + "problemMatcher": [], + } + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f3d9b12 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.9) + +set(VERSION "1.0.1") +set(RELEASE, 1) +set(PACKAGE_NAME "mktouch") +set(VENDOR "AA") +set(DESCRIPTION "Make directory's and files at the same time") + +set(CPACK_PACKAGE_CONTACT "Ade Attwood") + +project(${PACKAGE_NAME}) + +add_subdirectory (src) + +add_executable(mktouch ./src/App.cpp + ./src/Cli.cpp + ./src/main.cpp) + +FIND_PACKAGE(Boost COMPONENTS program_options filesystem REQUIRED) +INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) + +TARGET_LINK_LIBRARIES(mktouch ${Boost_LIBRARIES}) + +set(CPACK_PACKAGE_VERSION ${VERSION}) +set(CPACK_GENERATOR "DEB;ZIP;TGZ") +set(CPACK_PACKAGE_NAME ${PACKAGE_NAME}) +set(CPACK_PACKAGE_RELEASE ${RELEASE}) +set(CPACK_PACKAGE_VENDOR ${VENDOR}) +set(CPACK_PACKAGING_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX}) +set(CPACK_PACKAGE_DESCRIPTION ${DESCRIPTION}) + +set(CPACK_DEBIAN_PACKAGE_DEPENDS "libboost-program-options1.62.0,libboost-filesystem1.62.0,libboost-system1.62.0,libstdc++6,libgcc1,libc6") + +install(TARGETS mktouch DESTINATION bin) +install(DIRECTORY man DESTINATION .) + +include(CPack) diff --git a/apt-pkg.txt b/apt-pkg.txt new file mode 100644 index 0000000..fb153e6 --- /dev/null +++ b/apt-pkg.txt @@ -0,0 +1,6 @@ +libboost-filesystem1.62.0 +libboost-program-options1.62.0 +libboost-system1.62.0 +libc6 +libgcc1 +libstdc++6 diff --git a/builder b/builder new file mode 100755 index 0000000..f85bd73 --- /dev/null +++ b/builder @@ -0,0 +1,64 @@ +#!/bin/bash + +SRC_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"; + +DEBUG_DIR="$SRC_DIR/build/Debug"; +RELEASE_DIR="$SRC_DIR/build/Release"; + +case "$1" in + apt-pkg) + ldd build/Release/mktouch | awk '/=>/{print $(NF-1)}' | while read n; do dpkg-query -S $n | cut -d ":" -f 1; done | uniq | sort > apt-pkg.txt; + ;; + debug) + echo "CMAKE --> Debug"; + mkdir -p "$DEBUG_DIR"; + cd "$DEBUG_DIR"; + BUILD_TYPE="Debug"; + BUILD="true"; + MAKE="true"; + ;; + release) + echo "CMAKE --> Release"; + mkdir -p "$RELEASE_DIR"; + cd "$RELEASE_DIR"; + BUILD_TYPE="Release"; + BUILD="true"; + MAKE="true"; + ;; + package) + echo "CMAKE --> Release"; + mkdir -p "$RELEASE_DIR"; + cd "$RELEASE_DIR"; + BUILD_TYPE="Release"; + BUILD="true"; + MAKE="true"; + PACKAGE="true"; + ;; + clean) + echo "CMAKE --> Cleaning"; + rm -rf "$SRC_DIR/build"; + ;; + *) + echo "CMAKE --> Build not found"; + echo " -- apt-pkg"; + echo " -- debug"; + echo " -- release"; + echo " -- clean"; + exit 0; + ;; +esac + +if [ -n "$BUILD" ]; then + cp -R $SRC_DIR/man $(pwd); + cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE $SRC_DIR; +fi + +if [ -n "$MAKE" ]; then + echo "CMAKE --> Make"; + make; +fi + +if [ -n "$PACKAGE" ]; then + echo "CMAKE --> Package"; + make package; +fi diff --git a/man/man1/mktouch.1 b/man/man1/mktouch.1 new file mode 100644 index 0000000..19dee97 --- /dev/null +++ b/man/man1/mktouch.1 @@ -0,0 +1,36 @@ + +.TH mkdir 1 "03 feb 2018" "Version 1.0.1" "mktouch man page" +.SH NAME +mktouch \- Make directory's and files at the same time +.SH SYNOPSIS +mktouch +.I file-path file +[options] +.SH DESCRIPTION +Make directory's and files at the same time. +.SH OPTIONS +.PP +-f [ --force ] Force create the file even if it exsits +.PP +-h [ --help ] Show the help message +.PP +-V [ --version ] Display the version number +.SH EXAMPLES +.PP +.B mktouch /my/path +.RE +This command will just create the dir /my/path just like the mkdir command +.PP +.B mktouch /my/path file.txt +.RE +This will make the path like above and then create the file `file.txt` and put it into the directory +.PP +.B mktouch /my/path file.txt -f +.RE +This will do the same as above but if the file already exsits it will be overriten +.SH SEE ALSO +mkdir(1) touch(1) +.SH BUGS +No known bugs. +.SH AUTHOR +Ade Attwood (code@adeattwood.co.uk) diff --git a/src/App.cpp b/src/App.cpp new file mode 100644 index 0000000..db08cae --- /dev/null +++ b/src/App.cpp @@ -0,0 +1,107 @@ +#include +#include +#include + +#include + +#include "App.h" +#include "Cli.h" + +App::App() {} +App::App(const App& orig) {} +App::~App() {} + +int App::shutDown(int code) { + return code; +} + +void App::parseArgs(int argc, char** argv) { + namespace po = boost::program_options; + + po::options_description options("Options"); + options.add_options() + ("force,f", "Force create the file even if it exsits") + ("help,h", "Show this messages") + ("version,V", "Display the version number"); + + po::options_description arguments("Arguments"); + arguments.add_options() + ("file-path", po::value(), "The path where to create the file") + ("file", po::value(), "The file to create"); + + positionalOptions.add("file-path", 1); + positionalOptions.add("file", 1); + + desc.add(arguments).add(options); + + po::store(po::command_line_parser(argc, argv). + positional(positionalOptions). + options(desc). + run(), + vm); + + po::notify(vm); +} + +void App::showHelp() { + std::vector parts; + + parts.push_back("Usage: "); + parts.push_back(this->name); + + size_t N = positionalOptions.max_total_count(); + + if (N == std::numeric_limits::max()) { + std::vector args = this->_getUnlimitedPositionalArgs(positionalOptions); + parts.insert(parts.end(), args.begin(), args.end()); + } else { + for(size_t i = 0; i < N; ++i) { + parts.push_back(positionalOptions.name_for_position(i)); + } + } + + if (desc.options().size() > 0) { + parts.push_back("[options]"); + } + + std::ostringstream oss; + std::copy(parts.begin(), parts.end(), std::ostream_iterator(oss, " ")); + oss << '\n' << desc; + + std::cout << Cli::style(oss.str()); +} + +std::vector App::_getUnlimitedPositionalArgs(const po::positional_options_description& p) +{ + assert(p.max_total_count() == std::numeric_limits::max()); + + std::vector parts; + + const int MAX = 1000; + std::string last = p.name_for_position(MAX); + + for (size_t i = 0; true; ++i) { + std::string cur = p.name_for_position(i); + if (cur == last) { + parts.push_back(cur); + parts.push_back('[' + cur + ']'); + parts.push_back("..."); + return parts; + } + parts.push_back(cur); + } + return parts; // never get here +} + +void App::showVersion() { + std::cout << "Version: " << this->version << std::endl; +} + +bool App::hasOption(std::string name) { + return vm.count(name); +} + +po::variable_value App::getOption(std::string name) { + return vm[name]; +} + diff --git a/src/App.h b/src/App.h new file mode 100644 index 0000000..9879837 --- /dev/null +++ b/src/App.h @@ -0,0 +1,39 @@ +#ifndef APP_H +#define APP_H + +#include +#include +#include + +#include + +namespace po = boost::program_options; + +class App { + po::variables_map vm; + po::options_description desc; + po::positional_options_description positionalOptions; + + public: + std::string version = "1.0.1"; + std::string name = "mktouch"; + + App(); + App(const App& orig); + virtual ~App(); + + void parseArgs(int argc, char** argv); + + void showHelp(); + void showVersion(); + bool hasOption(std::string name); + int shutDown(int code); + + po::variable_value getOption(std::string name); + + private: + std::vector _getUnlimitedPositionalArgs(const po::positional_options_description& p); +}; + +#endif /* APP_H */ + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/Cli.cpp b/src/Cli.cpp new file mode 100644 index 0000000..0a993bf --- /dev/null +++ b/src/Cli.cpp @@ -0,0 +1,119 @@ +#include +#include +#include + +#include "Cli.h" + +Cli::Cli() {} +Cli::Cli(const Cli& orig) {} +Cli::~Cli() {} + +std::string Cli::style(std::string string) { + + Cli::replace(string, "{{eol}}", "\n"); + + Cli::replace(string, "{{", "\e["); + Cli::replace(string, "}}", "m"); + + Cli::replace(string, "cli_fg_red", "31"); + Cli::replace(string, "cli_fg_default", "39"); + Cli::replace(string, "cli_fg_green", "32"); + Cli::replace(string, "cli_fg_yellow", "33"); + Cli::replace(string, "cli_fg_blue", "34"); + Cli::replace(string, "cli_fg_white", "97"); + Cli::replace(string, "cli_fg_black", "30"); + + Cli::replace(string, "cli_bg_red", "41"); + Cli::replace(string, "cli_bg_default", "49"); + Cli::replace(string, "cli_bg_green", "42"); + Cli::replace(string, "cli_bg_yellow", "43"); + Cli::replace(string, "cli_bg_blue", "44"); + Cli::replace(string, "cli_bg_white", "107"); + Cli::replace(string, "cli_bg_black", "40"); + + Cli::replace(string, "cli_reset_normal", "0"); + Cli::replace(string, "cli_reset_bold", "21"); + Cli::replace(string, "cli_reset_dim", "22"); + Cli::replace(string, "cli_reset_underline", "24"); + Cli::replace(string, "cli_reset_blink", "25"); + Cli::replace(string, "cli_reset_invert", "27"); + Cli::replace(string, "cli_reset_hidden", "28"); + + Cli::replace(string, "cli_normal", "0"); + Cli::replace(string, "cli_bold", "1"); + Cli::replace(string, "cli_dim", "2"); + Cli::replace(string, "cli_underline", "4"); + Cli::replace(string, "cli_blink", "5"); + Cli::replace(string, "cli_invert", "7"); + Cli::replace(string, "cli_hidden", "8"); + + return string; +} + +bool Cli::replace(std::string& str, const std::string& from, const std::string& to) { + if(from.empty()) { + return false; + } + + size_t start_pos = 0; + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + + return true; +} + +void Cli::out(std::string output) { + std::cout << Cli::style(output); +} + +void Cli::hideCursor() { + Cli::out("\033[?25l"); +} + +void Cli::showCursor() { + Cli::out("\033[?25h"); +} + +void Cli::clearScreen() { + Cli::out("\033[2J"); +} + +void Cli::clearLine() { + Cli::out("\033[2K"); +} + +std::string Cli::in() { + std::string out; + std::cin >> out; + return out; +} + +std::string Cli::readLine(std::string question) { + Cli::out(question); + return Cli::in(); +} + +int Cli::askQuestion(std::string question, std::map questionOptions) { + question += "{{eol}}"; + + for (const auto& kv : questionOptions) { + question += "[" + std::to_string(kv.first) + "] " + kv.second + "{{eol}}"; + } + + + std::string out; + + while (out.empty()) { + std::string answer = Cli::readLine(question); + + if (questionOptions.find(std::stoi(answer)) == questionOptions.end()) { + Cli::out("Invalid option{{eol}}"); + } else { + out = answer; + } + } + + return std::stoi(out); +} \ No newline at end of file diff --git a/src/Cli.h b/src/Cli.h new file mode 100644 index 0000000..2417dc1 --- /dev/null +++ b/src/Cli.h @@ -0,0 +1,31 @@ + +#ifndef CLI_H +#define CLI_H + +#include +#include +#include + +class Cli { +public: + Cli(); + Cli(const Cli& orig); + virtual ~Cli(); + + static void out(std::string output); + static std::string in(); + static void hideCursor(); + static void showCursor(); + static void clearScreen(); + static void clearLine(); + + static std::string readLine(std::string question); + static int askQuestion(std::string qustion, std::map < int, std::string >); + static std::string style(std::string string); +private: + static bool replace(std::string& str, const std::string& from, const std::string& to); + +}; + +#endif /* CLI_H */ + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..65bfcb9 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,59 @@ +#include +#include +#include + +#include + +#include "App.h" +#include "Cli.h" + +namespace fs = boost::filesystem; + +namespace { + const size_t SUCCESS = 0; + const size_t ERROR = 1; + const size_t UNHANDLED_ERROR = 2; +} // namespace + +int main(int argc, char** argv) { + App app; + + try { + app.parseArgs(argc, argv); + } catch(std::exception& E) { + std::cout << "ERROR: " << E.what() << std::endl; + return app.shutDown(UNHANDLED_ERROR); + } + + if (app.hasOption("help")) { + app.showHelp(); + return app.shutDown(SUCCESS); + } + + if (app.hasOption("version")) { + app.showVersion(); + return app.shutDown(SUCCESS); + } + + if (!app.hasOption("file-path")) { + app.showHelp(); + return app.shutDown(ERROR); + } + + std::string filePath = app.getOption("file-path").as(); + fs::create_directories(filePath); + + if (app.hasOption("file")) { + std::string file = filePath + "/" + app.getOption("file").as(); + + if (!app.hasOption("force") && fs::exists(file)) { + std::cout << "ERROR: " << file << " already exists" << std::endl; + return app.shutDown(ERROR); + } + + std::ofstream outfile (file); + outfile.close(); + } + + return app.shutDown(SUCCESS); +};