From dc13ef5de9094eb2cecfb08a7e106d34d3276c0d Mon Sep 17 00:00:00 2001 From: Kubat <mael.martin31@gmail.com> Date: Tue, 9 Feb 2021 09:26:49 +0100 Subject: [PATCH] LUKA: Drop some code for the luka client. - Add the cli header lib for that client - Drop in a clang-format file - Basic luka.cpp --- .clang-format | 148 +++ README.md | 5 +- inc/cli/.github/FUNDING.yml | 12 + inc/cli/.github/workflows/ccpp.yml | 28 + inc/cli/.gitignore | 69 ++ inc/cli/LICENSE | 23 + inc/cli/boostasiocliasyncsession.h | 40 + inc/cli/boostasioremotecli.h | 39 + inc/cli/boostasioscheduler.h | 38 + inc/cli/cli.h | 1092 +++++++++++++++++++++++ inc/cli/clifilesession.h | 83 ++ inc/cli/clilocalsession.h | 65 ++ inc/cli/colorprofile.h | 76 ++ inc/cli/detail/boostasiolib.h | 49 + inc/cli/detail/commonprefix.h | 70 ++ inc/cli/detail/fromstring.h | 281 ++++++ inc/cli/detail/genericasioremotecli.h | 576 ++++++++++++ inc/cli/detail/genericasioscheduler.h | 87 ++ inc/cli/detail/genericcliasyncsession.h | 100 +++ inc/cli/detail/history.h | 164 ++++ inc/cli/detail/inputdevice.h | 72 ++ inc/cli/detail/inputhandler.h | 133 +++ inc/cli/detail/keyboard.h | 58 ++ inc/cli/detail/linuxkeyboard.h | 143 +++ inc/cli/detail/newboostasiolib.h | 83 ++ inc/cli/detail/newstandaloneasiolib.h | 82 ++ inc/cli/detail/oldboostasiolib.h | 77 ++ inc/cli/detail/oldstandaloneasiolib.h | 80 ++ inc/cli/detail/rang.h | 298 +++++++ inc/cli/detail/server.h | 159 ++++ inc/cli/detail/split.h | 237 +++++ inc/cli/detail/standaloneasiolib.h | 49 + inc/cli/detail/terminal.h | 213 +++++ inc/cli/detail/winkeyboard.h | 132 +++ inc/cli/filehistorystorage.h | 85 ++ inc/cli/historystorage.h | 54 ++ inc/cli/loopscheduler.h | 103 +++ inc/cli/scheduler.h | 55 ++ inc/cli/standaloneasiocliasyncsession.h | 40 + inc/cli/standaloneasioremotecli.h | 40 + inc/cli/standaloneasioscheduler.h | 38 + inc/cli/volatilehistorystorage.h | 68 ++ src/Makefile.am | 6 +- src/Makefile.in | 27 +- src/main/luka.cpp | 33 + utils/licenses/CLI.LICENSE | 28 + 46 files changed, 5432 insertions(+), 6 deletions(-) create mode 100644 .clang-format create mode 100644 inc/cli/.github/FUNDING.yml create mode 100644 inc/cli/.github/workflows/ccpp.yml create mode 100644 inc/cli/.gitignore create mode 100644 inc/cli/LICENSE create mode 100644 inc/cli/boostasiocliasyncsession.h create mode 100644 inc/cli/boostasioremotecli.h create mode 100644 inc/cli/boostasioscheduler.h create mode 100644 inc/cli/cli.h create mode 100644 inc/cli/clifilesession.h create mode 100644 inc/cli/clilocalsession.h create mode 100644 inc/cli/colorprofile.h create mode 100644 inc/cli/detail/boostasiolib.h create mode 100644 inc/cli/detail/commonprefix.h create mode 100644 inc/cli/detail/fromstring.h create mode 100644 inc/cli/detail/genericasioremotecli.h create mode 100644 inc/cli/detail/genericasioscheduler.h create mode 100644 inc/cli/detail/genericcliasyncsession.h create mode 100644 inc/cli/detail/history.h create mode 100644 inc/cli/detail/inputdevice.h create mode 100644 inc/cli/detail/inputhandler.h create mode 100644 inc/cli/detail/keyboard.h create mode 100644 inc/cli/detail/linuxkeyboard.h create mode 100644 inc/cli/detail/newboostasiolib.h create mode 100644 inc/cli/detail/newstandaloneasiolib.h create mode 100644 inc/cli/detail/oldboostasiolib.h create mode 100644 inc/cli/detail/oldstandaloneasiolib.h create mode 100644 inc/cli/detail/rang.h create mode 100644 inc/cli/detail/server.h create mode 100644 inc/cli/detail/split.h create mode 100644 inc/cli/detail/standaloneasiolib.h create mode 100644 inc/cli/detail/terminal.h create mode 100644 inc/cli/detail/winkeyboard.h create mode 100644 inc/cli/filehistorystorage.h create mode 100644 inc/cli/historystorage.h create mode 100644 inc/cli/loopscheduler.h create mode 100644 inc/cli/scheduler.h create mode 100644 inc/cli/standaloneasiocliasyncsession.h create mode 100644 inc/cli/standaloneasioremotecli.h create mode 100644 inc/cli/standaloneasioscheduler.h create mode 100644 inc/cli/volatilehistorystorage.h create mode 100644 src/main/luka.cpp create mode 100644 utils/licenses/CLI.LICENSE diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..f6164387 --- /dev/null +++ b/.clang-format @@ -0,0 +1,148 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveBitFields: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: trye +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE +... + diff --git a/README.md b/README.md index f4cd73a4..9529448e 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,10 @@ Note that because of the use of `curl` and `get`, building behind a proxy may be If you are developping for lektor, you will need: -- the [astyle](http://astyle.sourceforge.net/) command line utility, for a beautifull and uniform coding style. +- the [astyle](http://astyle.sourceforge.net/) command line utility, for a beautifull and uniform + coding style for C code. +- the [clang-format](https://releases.llvm.org/download.html) command line utility, for a + beautifull and uniform C++ code style. > If you are having problems build lektor (AC macros not defined), try copy the > `.m4` files from `/usr/share/aclocal` to `config/m4`. diff --git a/inc/cli/.github/FUNDING.yml b/inc/cli/.github/FUNDING.yml new file mode 100644 index 00000000..ffc06a19 --- /dev/null +++ b/inc/cli/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: daniele77 +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/inc/cli/.github/workflows/ccpp.yml b/inc/cli/.github/workflows/ccpp.yml new file mode 100644 index 00000000..5d151e62 --- /dev/null +++ b/inc/cli/.github/workflows/ccpp.yml @@ -0,0 +1,28 @@ +name: C/C++ CI of Cli + +on: [push,pull_request] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: setup dependencies + run: | + sudo apt-get -y update + sudo apt-get -y install libboost-all-dev libasio-dev + - name: run cmake + run: | + mkdir build + cd build + cmake .. -DCLI_BuildTests=ON -DCLI_BuildExamples=ON -DCLI_UseBoostAsio=ON + - name: make + run: | + cd /home/runner/work/cli/cli/build + make all + - name: run tests + run: | + cd /home/runner/work/cli/cli/build/test + ./test_suite diff --git a/inc/cli/.gitignore b/inc/cli/.gitignore new file mode 100644 index 00000000..2da4bda0 --- /dev/null +++ b/inc/cli/.gitignore @@ -0,0 +1,69 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Cmake Files +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +build +cmake-build-* + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +Debug +Release +test/test_suite +asyncsession +complete +filesession +pluginmanager +simplelocalsession + +# Project files +.cproject +.project +.vscode/ +.vs +*.opensdf +*.vcxproj.filters +*.sdf +*.user +*.db +.svn + +# Doxygen +doc/doxy/html + +# Test files +cli_test_history \ No newline at end of file diff --git a/inc/cli/LICENSE b/inc/cli/LICENSE new file mode 100644 index 00000000..127a5bc3 --- /dev/null +++ b/inc/cli/LICENSE @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/inc/cli/boostasiocliasyncsession.h b/inc/cli/boostasiocliasyncsession.h new file mode 100644 index 00000000..e2dc607e --- /dev/null +++ b/inc/cli/boostasiocliasyncsession.h @@ -0,0 +1,40 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_BOOSTASIOCLIASYNCSESSION_H_ +#define CLI_BOOSTASIOCLIASYNCSESSION_H_ + +#include "detail/genericcliasyncsession.h" +#include "detail/boostasiolib.h" + + +namespace cli { using BoostAsioCliAsyncSession = detail::GenericCliAsyncSession<detail::BoostAsioLib>; } + +#endif // CLI_BOOSTASIOCLIASYNCSESSION_H_ + diff --git a/inc/cli/boostasioremotecli.h b/inc/cli/boostasioremotecli.h new file mode 100644 index 00000000..f9d5acc6 --- /dev/null +++ b/inc/cli/boostasioremotecli.h @@ -0,0 +1,39 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_BOOSTASIOREMOTECLI_H_ +#define CLI_BOOSTASIOREMOTECLI_H_ + +#include "detail/genericasioremotecli.h" +#include "detail/boostasiolib.h" + +namespace cli { using BoostAsioCliTelnetServer = detail::CliGenericTelnetServer<detail::BoostAsioLib>; } + +#endif // CLI_BOOSTASIOREMOTECLI_H_ + diff --git a/inc/cli/boostasioscheduler.h b/inc/cli/boostasioscheduler.h new file mode 100644 index 00000000..f4a872a5 --- /dev/null +++ b/inc/cli/boostasioscheduler.h @@ -0,0 +1,38 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_BOOSTASIOSCHEDULER_H_ +#define CLI_BOOSTASIOSCHEDULER_H_ + +#include "detail/genericasioscheduler.h" +#include "detail/boostasiolib.h" + +namespace cli { using BoostAsioScheduler = detail::GenericAsioScheduler<detail::BoostAsioLib>; } + +#endif // CLI_BOOSTASIOSCHEDULER_H_ diff --git a/inc/cli/cli.h b/inc/cli/cli.h new file mode 100644 index 00000000..49d78fc5 --- /dev/null +++ b/inc/cli/cli.h @@ -0,0 +1,1092 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_H_ +#define CLI_H_ + +#include <iostream> +#include <string> +#include <vector> +#include <memory> +#include <functional> +#include <algorithm> +#include <cctype> // std::isspace +#include <type_traits> +#include "colorprofile.h" +#include "detail/history.h" +#include "detail/split.h" +#include "detail/fromstring.h" +#include "historystorage.h" +#include "volatilehistorystorage.h" + +// #define CLI_DEPRECATED_API + +namespace cli +{ + + // ******************************************************************** + + template < typename T > struct TypeDesc { static const char* Name() { return ""; } }; + template <> struct TypeDesc< char > { static const char* Name() { return "<char>"; } }; + template <> struct TypeDesc< unsigned char > { static const char* Name() { return "<unsigned char>"; } }; + template <> struct TypeDesc< signed char > { static const char* Name() { return "<signed char>"; } }; + template <> struct TypeDesc< short > { static const char* Name() { return "<short>"; } }; + template <> struct TypeDesc< unsigned short > { static const char* Name() { return "<unsigned short>"; } }; + template <> struct TypeDesc< int > { static const char* Name() { return "<int>"; } }; + template <> struct TypeDesc< unsigned int > { static const char* Name() { return "<unsigned int>"; } }; + template <> struct TypeDesc< long > { static const char* Name() { return "<long>"; } }; + template <> struct TypeDesc< unsigned long > { static const char* Name() { return "<unsigned long>"; } }; + template <> struct TypeDesc< long long > { static const char* Name() { return "<long long>"; } }; + template <> struct TypeDesc< unsigned long long > { static const char* Name() { return "<unsigned long long>"; } }; + template <> struct TypeDesc< float > { static const char* Name() { return "<float>"; } }; + template <> struct TypeDesc< double > { static const char* Name() { return "<double>"; } }; + template <> struct TypeDesc< long double > { static const char* Name() { return "<long double>"; } }; + template <> struct TypeDesc< bool > { static const char* Name() { return "<bool>"; } }; + template <> struct TypeDesc< std::string > { static const char* Name() { return "<string>"; } }; + + // ******************************************************************** + + // forward declarations + class Menu; + class CliSession; + + + class Cli + { + + // inner class to provide a global output stream + class OutStream + { + public: + template <typename T> + OutStream& operator << (const T& msg) + { + for (auto out: ostreams) + *out << msg; + return *this; + } + + // this is the type of std::cout + typedef std::basic_ostream<char, std::char_traits<char> > CoutType; + // this is the function signature of std::endl + typedef CoutType& (*StandardEndLine)(CoutType&); + + // takes << std::endl + OutStream& operator << (StandardEndLine manip) + { + for (auto out: ostreams) + manip(*out); + return *this; + } + + private: + friend class Cli; + + void Register(std::ostream& o) + { + ostreams.push_back(&o); + } + void UnRegister(std::ostream& o) + { + ostreams.erase(std::remove(ostreams.begin(), ostreams.end(), &o), ostreams.end()); + } + + std::vector<std::ostream*> ostreams; + }; + // end inner class + + public: + Cli( + std::unique_ptr<Menu>&& _rootMenu, + std::function< void(std::ostream&)> _exitAction = {}, + std::unique_ptr<HistoryStorage>&& historyStorage = std::make_unique<VolatileHistoryStorage>() + ) : + globalHistoryStorage(std::move(historyStorage)), + rootMenu(std::move(_rootMenu)), + exitAction(_exitAction) + { + } + + Cli(std::unique_ptr<Menu> _rootMenu, std::unique_ptr<HistoryStorage> historyStorage) : + Cli(std::move(_rootMenu), {}, std::move(historyStorage)) + { + } + + // disable value semantics + Cli(const Cli&) = delete; + Cli& operator = (const Cli&) = delete; + + Menu* RootMenu() { return rootMenu.get(); } + void ExitAction( const std::function< void(std::ostream&)>& action ) { exitAction = action; } + void ExitAction( std::ostream& out ) + { + if ( exitAction ) + exitAction( out ); + } + void StdExceptionHandler(const std::function< void(std::ostream&, const std::string& cmd, const std::exception&) >& handler) + { + exceptionHandler = handler; + } + void StdExceptionHandler(std::ostream& out, const std::string& cmd, const std::exception& e) + { + if (exceptionHandler) + exceptionHandler(out, cmd, e); + else + out << e.what() << '\n'; + } + + static void Register(std::ostream& o) { cout().Register(o); } + static void UnRegister(std::ostream& o) { cout().UnRegister(o); } + + static OutStream& cout() + { + static OutStream s; + return s; + } + + void StoreCommands(const std::vector<std::string>& cmds) + { + globalHistoryStorage->Store(cmds); + } + + std::vector<std::string> GetCommands() const + { + return globalHistoryStorage->Commands(); + } + + private: + std::unique_ptr<HistoryStorage> globalHistoryStorage; + std::unique_ptr<Menu> rootMenu; // just to keep it alive + std::function<void(std::ostream&)> exitAction; + std::function<void(std::ostream&, const std::string& cmd, const std::exception& )> exceptionHandler; + }; + + // ******************************************************************** + + class Command + { + public: + explicit Command(const std::string& _name) : name(_name), enabled(true) {} + virtual ~Command() = default; + virtual void Enable() { enabled = true; } + virtual void Disable() { enabled = false; } + virtual bool Exec(const std::vector<std::string>& cmdLine, CliSession& session) = 0; + virtual void Help(std::ostream& out) const = 0; + // Returns the collection of completions relatives to this command. + // For simple commands, provides a base implementation that use the name of the command + // for aggregate commands (i.e., Menu), the function is redefined to give the menu command + // and the subcommand recursively + virtual std::vector<std::string> GetCompletionRecursive(const std::string& line) const + { + if (!enabled) return {}; + if (name.rfind(line, 0) == 0) return {name}; // name starts_with line + else return {}; + } + protected: + const std::string& Name() const { return name; } + bool IsEnabled() const { return enabled; } + private: + const std::string name; + bool enabled; + }; + + // ******************************************************************** + + // free utility function to get completions from a list of commands and the current line + inline std::vector<std::string> GetCompletions( + const std::shared_ptr<std::vector<std::shared_ptr<Command>>>& cmds, + const std::string& currentLine) + { + std::vector<std::string> result; + std::for_each(cmds->begin(), cmds->end(), + [¤tLine,&result](const auto& cmd) + { + auto c = cmd->GetCompletionRecursive(currentLine); + result.insert( + result.end(), + std::make_move_iterator(c.begin()), + std::make_move_iterator(c.end()) + ); + } + ); + return result; + } + + // ******************************************************************** + + class CliSession + { + public: + CliSession(Cli& _cli, std::ostream& _out, std::size_t historySize = 100); + virtual ~CliSession() { cli.UnRegister(out); } + + // disable value semantics + CliSession(const CliSession&) = delete; + CliSession& operator = (const CliSession&) = delete; + + void Feed( const std::string& cmd ); + + void Prompt(); + + void Current(Menu* menu) { current = menu; } + + std::ostream& OutStream() { return out; } + + void Help() const; + + void Exit() + { + exitAction(out); + cli.ExitAction(out); + + auto cmds = history.GetCommands(); + cli.StoreCommands(cmds); + } + + void ExitAction(const std::function<void(std::ostream&)>& action) + { + exitAction = action; + } + + void ShowHistory() const { history.Show(out); } + + std::string PreviousCmd(const std::string& line) + { + return history.Previous(line); + } + + std::string NextCmd() + { + return history.Next(); + } + + std::vector<std::string> GetCompletions(std::string currentLine) const; + + private: + + Cli& cli; + Menu* current; + std::unique_ptr<Menu> globalScopeMenu; + std::ostream& out; + std::function< void(std::ostream&)> exitAction = []( std::ostream& ){}; + detail::History history; + }; + + // ******************************************************************** + + class CmdHandler + { + public: + using CmdVec = std::vector<std::shared_ptr<Command>>; + CmdHandler() : descriptor(std::make_shared<Descriptor>()) {} + CmdHandler(std::weak_ptr<Command> c, std::weak_ptr<CmdVec> v) : + descriptor(std::make_shared<Descriptor>(c, v)) + {} + void Enable() { if (descriptor) descriptor->Enable(); } + void Disable() { if (descriptor) descriptor->Disable(); } + void Remove() { if (descriptor) descriptor->Remove(); } + private: + struct Descriptor + { + Descriptor() {} + Descriptor(const std::weak_ptr<Command>& c, const std::weak_ptr<CmdVec>& v) : + cmd(c), cmds(v) + {} + void Enable() + { + if (auto c = cmd.lock()) + c->Enable(); + } + void Disable() + { + if(auto c = cmd.lock()) + c->Disable(); + } + void Remove() + { + auto scmd = cmd.lock(); + auto scmds = cmds.lock(); + if (scmd && scmds) + { + auto i = std::find_if( + scmds->begin(), + scmds->end(), + [&](const auto& c){ return c.get() == scmd.get(); } + ); + if (i != scmds->end()) + scmds->erase(i); + } + } + std::weak_ptr<Command> cmd; + std::weak_ptr<CmdVec> cmds; + }; + std::shared_ptr<Descriptor> descriptor; + }; + + // ******************************************************************** + + class Menu : public Command + { + public: + // disable value semantics + Menu(const Menu&) = delete; + Menu& operator = (const Menu&) = delete; + + Menu() : Command({}), parent(nullptr), description(), cmds(std::make_shared<Cmds>()) {} + + Menu(const std::string& _name, const std::string& desc = "(menu)") : + Command(_name), parent(nullptr), description(desc), cmds(std::make_shared<Cmds>()) + {} + + template <typename F> + CmdHandler Insert(const std::string& cmdName, F f, const std::string& help = "", const std::vector<std::string>& parDesc={}) + { + // dispatch to private Insert methods + return Insert(cmdName, help, parDesc, f, &F::operator()); + } + + template <typename F> + CmdHandler Insert(const std::string& cmdName, const std::vector<std::string>& parDesc, F f, const std::string& help = "") + { + // dispatch to private Insert methods + return Insert(cmdName, help, parDesc, f, &F::operator()); + } + +#ifdef CLI_DEPRECATED_API + template <typename F> + [[deprecated("Use the method Insert instead")]] + void Add(const std::string& cmdName, F f, const std::string& help = "") + { + // dispatch to private Add methods + Add(cmdName, help, f, &F::operator()); + } + + [[deprecated("Use the method Insert instead")]] + void Add(std::unique_ptr<Command>&& cmd) + { + std::shared_ptr<Command> s(std::move(cmd)); + cmds->push_back(s); + } + + [[deprecated("Use the method Insert instead")]] + void Add(std::unique_ptr<Menu>&& menu) + { + std::shared_ptr<Menu> s(std::move(menu)); + s->parent = this; + cmds->push_back(s); + } +#endif // CLI_DEPRECATED_API + + CmdHandler Insert(std::unique_ptr<Command>&& cmd) + { + std::shared_ptr<Command> scmd(std::move(cmd)); + CmdHandler c(scmd, cmds); + cmds->push_back(scmd); + return c; + } + + CmdHandler Insert(std::unique_ptr<Menu>&& menu) + { + std::shared_ptr<Menu> smenu(std::move(menu)); + CmdHandler c(smenu, cmds); + smenu->parent = this; + cmds->push_back(smenu); + return c; + } + + bool Exec(const std::vector<std::string>& cmdLine, CliSession& session) override + { + if (!IsEnabled()) + return false; + if (cmdLine[0] == Name()) + { + if (cmdLine.size() == 1) + { + session.Current(this); + return true; + } + else + { + // check also for subcommands + std::vector<std::string > subCmdLine(cmdLine.begin()+1, cmdLine.end()); + for (auto& cmd: *cmds) + if (cmd->Exec( subCmdLine, session )) return true; + } + } + return false; + } + + bool ScanCmds(const std::vector<std::string>& cmdLine, CliSession& session) + { + if (!IsEnabled()) return false; + for (auto& cmd: *cmds) + if (cmd->Exec(cmdLine, session)) return true; + if (parent && parent->Exec(cmdLine, session)) return true; + return false; + } + + std::string Prompt() const + { + return Name(); + } + + void MainHelp(std::ostream& out) + { + if (!IsEnabled()) return; + for (const auto& cmd: *cmds) + cmd->Help(out); + if (parent) parent->Help(out); + } + + void Help(std::ostream& out) const override + { + if (!IsEnabled()) return; + out << " - " << Name() << "\n\t" << description << "\n"; + } + + // returns: + // - the completions of this menu command + // - the recursive completions of subcommands + // - the recursive completions of parent menu + std::vector<std::string> GetCompletions(const std::string& currentLine) const + { + auto result = cli::GetCompletions(cmds, currentLine); + if (parent) + { + auto c = parent->GetCompletionRecursive(currentLine); + result.insert(result.end(), std::make_move_iterator(c.begin()), std::make_move_iterator(c.end())); + } + return result; + } + + // returns: + // - the completion of this menu command + // - the recursive completions of the subcommands + virtual std::vector<std::string> GetCompletionRecursive(const std::string& line) const override + { + if (line.rfind(Name(), 0) == 0) // line starts_with Name() + { + auto rest = line; + rest.erase(0, Name().size()); + // trim_left(rest); + rest.erase(rest.begin(), std::find_if(rest.begin(), rest.end(), [](int ch) { return !std::isspace(ch); })); + std::vector<std::string> result; + for (const auto& cmd: *cmds) + { + auto cs = cmd->GetCompletionRecursive(rest); + for (const auto& c: cs) + result.push_back(Name() + ' ' + c); // concat submenu with command + } + return result; + } + return Command::GetCompletionRecursive(line); + } + + private: + +#ifdef CLI_DEPRECATED_API + template <typename F, typename R> + void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(std::ostream& out) const); + + template <typename F, typename R, typename A1> + void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(A1, std::ostream& out) const); + + template <typename F, typename R, typename A1, typename A2> + void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(A1, A2, std::ostream& out) const); + + template <typename F, typename R, typename A1, typename A2, typename A3> + void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(A1, A2, A3, std::ostream& out) const); + + template <typename F, typename R, typename A1, typename A2, typename A3, typename A4> + void Add(const std::string& name, const std::string& help, F& f,R (F::*mf)(A1, A2, A3, A4, std::ostream& out) const); +#endif // CLI_DEPRECATED_API + + template <typename F, typename R, typename ... Args> + CmdHandler Insert(const std::string& name, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, Args...) const); + + template <typename F, typename R> + CmdHandler Insert(const std::string& name, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, const std::vector<std::string>&) const); + + template <typename F, typename R> + CmdHandler Insert(const std::string& name, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, std::vector<std::string>) const); + + Menu* parent; + const std::string description; + // using shared_ptr instead of unique_ptr to get a weak_ptr + // for the CmdHandler::Descriptor + using Cmds = std::vector<std::shared_ptr<Command>>; + std::shared_ptr<Cmds> cmds; + }; + + // ******************************************************************** + +#ifdef CLI_DEPRECATED_API + + class FuncCmd : public Command + { + public: + // disable value semantics + FuncCmd( const FuncCmd& ) = delete; + FuncCmd& operator = ( const FuncCmd& ) = delete; + + FuncCmd( + const std::string& _name, + std::function< void( std::ostream& )> _function, + const std::string& desc = "" + ) : Command( _name ), function( _function ), description( desc ) + { + } + bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override + { + if ( cmdLine.size() != 1 ) return false; + if ( cmdLine[ 0 ] == Name() ) + { + function( session.OutStream() ); + return true; + } + + return false; + } + void Help( std::ostream& out ) const override + { + out << " - " << Name() << "\n\t" << description << "\n"; + } + private: + const std::function< void( std::ostream& ) > function; + const std::string description; + }; + + template < typename T > + class FuncCmd1 : public Command + { + public: + // disable value semantics + FuncCmd1( const FuncCmd1& ) = delete; + FuncCmd1& operator = ( const FuncCmd1& ) = delete; + + FuncCmd1( + const std::string& _name, + std::function< void( T, std::ostream& ) > _function, + const std::string& desc = "" + ) : Command( _name ), function( _function ), description( desc ) + { + } + bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override + { + if ( cmdLine.size() != 2 ) return false; + if ( Name() == cmdLine[ 0 ] ) + { + try + { + T arg = detail::from_string<T>( cmdLine[ 1 ] ); + function( arg, session.OutStream() ); + } + catch (std::bad_cast&) + { + return false; + } + return true; + } + + return false; + } + void Help( std::ostream& out ) const override + { + out << " - " << Name() + << " " << TypeDesc< T >::Name() + << "\n\t" << description << "\n"; + } + private: + const std::function< void( T, std::ostream& )> function; + const std::string description; + }; + + template < typename T1, typename T2 > + class FuncCmd2 : public Command + { + public: + // disable value semantics + FuncCmd2( const FuncCmd2& ) = delete; + FuncCmd2& operator = ( const FuncCmd2& ) = delete; + + FuncCmd2( + const std::string& _name, + std::function< void( T1, T2, std::ostream& ) > _function, + const std::string& desc = "2 parameter command" + ) : Command( _name ), function( _function ), description( desc ) + { + } + bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override + { + if ( cmdLine.size() != 3 ) return false; + if ( Name() == cmdLine[ 0 ] ) + { + try + { + T1 arg1 = detail::from_string<T1>( cmdLine[ 1 ] ); + T2 arg2 = detail::from_string<T2>( cmdLine[ 2 ] ); + function( arg1, arg2, session.OutStream() ); + } + catch (std::bad_cast&) + { + return false; + } + return true; + } + + return false; + } + void Help( std::ostream& out ) const override + { + out << " - " << Name() + << " " << TypeDesc< T1 >::Name() + << " " << TypeDesc< T2 >::Name() + << "\n\t" << description << "\n"; + } + private: + const std::function< void( T1, T2, std::ostream& )> function; + const std::string description; + }; + + template < typename T1, typename T2, typename T3 > + class FuncCmd3 : public Command + { + public: + // disable value semantics + FuncCmd3( const FuncCmd3& ) = delete; + FuncCmd3& operator = ( const FuncCmd3& ) = delete; + + FuncCmd3( + const std::string& _name, + std::function< void( T1, T2, T3, std::ostream& ) > _function, + const std::string& desc = "3 parameters command" + ) : Command( _name ), function( _function ), description( desc ) + { + } + bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override + { + if ( cmdLine.size() != 4 ) return false; + if ( Name() == cmdLine[ 0 ] ) + { + try + { + T1 arg1 = detail::from_string<T1>( cmdLine[ 1 ] ); + T2 arg2 = detail::from_string<T2>( cmdLine[ 2 ] ); + T3 arg3 = detail::from_string<T3>( cmdLine[ 3 ] ); + function( arg1, arg2, arg3, session.OutStream() ); + } + catch (std::bad_cast&) + { + return false; + } + return true; + } + + return false; + } + void Help( std::ostream& out ) const override + { + out << " - " << Name() + << " " << TypeDesc< T1 >::Name() + << " " << TypeDesc< T2 >::Name() + << " " << TypeDesc< T3 >::Name() + << "\n\t" << description << "\n"; + } + private: + const std::function< void( T1, T2, T3, std::ostream& )> function; + const std::string description; + }; + + template < typename T1, typename T2, typename T3, typename T4 > + class FuncCmd4 : public Command + { + public: + // disable value semantics + FuncCmd4( const FuncCmd4& ) = delete; + FuncCmd4& operator = ( const FuncCmd4& ) = delete; + + FuncCmd4( + const std::string& _name, + std::function< void( T1, T2, T3, T4, std::ostream& ) > _function, + const std::string& desc = "4 parameters command" + ) : Command( _name ), function( _function ), description( desc ) + { + } + bool Exec( const std::vector< std::string >& cmdLine, CliSession& session ) override + { + if ( cmdLine.size() != 5 ) return false; + if ( Name() == cmdLine[ 0 ] ) + { + try + { + T1 arg1 = detail::from_string<T1>( cmdLine[ 1 ] ); + T2 arg2 = detail::from_string<T2>( cmdLine[ 2 ] ); + T3 arg3 = detail::from_string<T3>( cmdLine[ 3 ] ); + T4 arg4 = detail::from_string<T4>( cmdLine[ 4 ] ); + function( arg1, arg2, arg3, arg4, session.OutStream() ); + } + catch (std::bad_cast&) + { + return false; + } + return true; + } + + return false; + } + void Help( std::ostream& out ) const override + { + out << " - " << Name() + << " " << TypeDesc< T1 >::Name() + << " " << TypeDesc< T2 >::Name() + << " " << TypeDesc< T3 >::Name() + << " " << TypeDesc< T4 >::Name() + << "\n\t" << description << "\n"; + } + private: + const std::function< void( T1, T2, T3, T4, std::ostream& )> function; + const std::string description; + }; + +#endif // CLI_DEPRECATED_API + + // ******************************************* + + template <typename F, typename ... Args> + struct Select; + + template <typename F, typename P, typename ... Args> + struct Select<F, P, Args...> + { + template <typename InputIt> + static void Exec(const F& f, InputIt first, InputIt last) + { + assert( first != last ); + assert( std::distance(first, last) == 1+sizeof...(Args) ); + const P p = detail::from_string<typename std::decay<P>::type>(*first); + auto g = [&](auto ... pars){ f(p, pars...); }; + Select<decltype(g), Args...>::Exec(g, std::next(first), last); + } + }; + + template <typename F> + struct Select<F> + { + template <typename InputIt> + static void Exec(const F& f, InputIt first, InputIt last) + { + // silence the unused warning in release mode when assert is disabled + static_cast<void>(first); + static_cast<void>(last); + + assert(first == last); + + f(); + } + }; + + template <typename ... Args> + struct PrintDesc; + + template <typename P, typename ... Args> + struct PrintDesc<P, Args...> + { + static void Dump(std::ostream& out) + { + out << " " << TypeDesc< typename std::decay<P>::type >::Name(); + PrintDesc<Args...>::Dump(out); + } + }; + + template <> + struct PrintDesc<> + { + static void Dump(std::ostream& /*out*/) {} + }; + + // ******************************************* + + template <typename F, typename ... Args> + class VariadicFunctionCommand : public Command + { + public: + // disable value semantics + VariadicFunctionCommand(const VariadicFunctionCommand&) = delete; + VariadicFunctionCommand& operator = (const VariadicFunctionCommand&) = delete; + + VariadicFunctionCommand( + const std::string& _name, + F fun, + const std::string& desc, + const std::vector<std::string>& parDesc + ) + : Command(_name), func(std::move(fun)), description(desc), parameterDesc(parDesc) + { + } + + bool Exec(const std::vector< std::string >& cmdLine, CliSession& session) override + { + if (!IsEnabled()) return false; + const std::size_t paramSize = sizeof...(Args); + if (cmdLine.size() != paramSize+1) return false; + if (Name() == cmdLine[0]) + { + try + { + auto g = [&](auto ... pars){ func( session.OutStream(), pars... ); }; + Select<decltype(g), Args...>::Exec(g, std::next(cmdLine.begin()), cmdLine.end()); + } + catch (std::bad_cast&) + { + return false; + } + return true; + } + return false; + } + + void Help(std::ostream& out) const override + { + if (!IsEnabled()) return; + out << " - " << Name(); + if (parameterDesc.empty()) + PrintDesc<Args...>::Dump(out); + for (auto& s: parameterDesc) + out << " <" << s << '>'; + out << "\n\t" << description << "\n"; + } + + private: + + const F func; + const std::string description; + const std::vector<std::string> parameterDesc; + }; + + + template <typename F> + class FreeformCommand : public Command + { + public: + // disable value semantics + FreeformCommand(const FreeformCommand&) = delete; + FreeformCommand& operator = (const FreeformCommand&) = delete; + + FreeformCommand( + const std::string& _name, + F fun, + const std::string& desc = "unknown command", + const std::vector<std::string>& parDesc = {} + ) + : Command(_name), func(std::move(fun)), description(desc), parameterDesc(parDesc) + { + } + + bool Exec(const std::vector< std::string >& cmdLine, CliSession& session) override + { + if (!IsEnabled()) return false; + assert(!cmdLine.empty()); + if (Name() == cmdLine[0]) + { + func(session.OutStream(), std::vector<std::string>(std::next(cmdLine.begin()), cmdLine.end())); + return true; + } + return false; + } + void Help(std::ostream& out) const override + { + if (!IsEnabled()) return; + out << " - " << Name(); + for (auto& s: parameterDesc) + out << " <" << s << '>'; + out << "\n\t" << description << "\n"; + } + + private: + + const F func; + const std::string description; + const std::vector<std::string> parameterDesc; + }; + + + // ******************************************************************** + + // CliSession implementation + + inline CliSession::CliSession(Cli& _cli, std::ostream& _out, std::size_t historySize) : + cli(_cli), + current(cli.RootMenu()), + globalScopeMenu(std::make_unique< Menu >()), + out(_out), + history(historySize) + { + history.LoadCommands(cli.GetCommands()); + + cli.Register(out); + globalScopeMenu->Insert( + "help", + [this](std::ostream&){ Help(); }, + "This help message" + ); + globalScopeMenu->Insert( + "exit", + [this](std::ostream&){ Exit(); }, + "Quit the session" + ); +#ifdef CLI_HISTORY_CMD + globalScopeMenu->Insert( + "history", + [this](std::ostream&){ ShowHistory(); }, + "Show the history" + ); +#endif + } + + inline void CliSession::Feed(const std::string& cmd) + { + std::vector<std::string> strs; + detail::split(strs, cmd); + if (strs.empty()) return; // just hit enter + + history.NewCommand(cmd); // add anyway to history + + try + { + + // global cmds check + bool found = globalScopeMenu->ScanCmds(strs, *this); + + // root menu recursive cmds check + if (!found) found = current->ScanCmds(std::move(strs), *this); // last use of strs + + if (!found) // error msg if not found + out << "wrong command: " << cmd << '\n'; + } + catch(const std::exception& e) + { + cli.StdExceptionHandler(out, cmd, e); + } + catch(...) + { + out << "Cli. Unknown exception caught handling command line \"" + << cmd + << "\"\n"; + } + + return; + } + + inline void CliSession::Prompt() + { + out << beforePrompt + << current->Prompt() + << afterPrompt + << "> " + << std::flush; + } + + inline void CliSession::Help() const + { + out << "Commands available:\n"; + globalScopeMenu->MainHelp(out); + current -> MainHelp( out ); + } + + inline std::vector<std::string> CliSession::GetCompletions(std::string currentLine) const + { + // trim_left(currentLine); + currentLine.erase(currentLine.begin(), std::find_if(currentLine.begin(), currentLine.end(), [](int ch) { return !std::isspace(ch); })); + auto v1 = globalScopeMenu->GetCompletions(currentLine); + auto v3 = current->GetCompletions(currentLine); + v1.insert(v1.end(), std::make_move_iterator(v3.begin()), std::make_move_iterator(v3.end())); + + // removes duplicates (std::unique requires a sorted container) + std::sort(v1.begin(), v1.end()); + auto ip = std::unique(v1.begin(), v1.end()); + v1.resize(static_cast<std::size_t>(std::distance(v1.begin(), ip))); + + return v1; + } + + // Menu implementation + +#ifdef CLI_DEPRECATED_API + template < typename F, typename R > + void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(std::ostream& out) const ) + { + cmds->push_back(std::make_shared<FuncCmd>(name, f, help)); + } + + template < typename F, typename R, typename A1 > + void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(A1, std::ostream& out) const ) + { + cmds->push_back(std::make_shared<FuncCmd1<A1>>(name, f, help)); + } + + template < typename F, typename R, typename A1, typename A2 > + void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(A1, A2, std::ostream& out) const ) + { + cmds->push_back(std::make_shared<FuncCmd2<A1, A2>>(name, f, help)); + } + + template < typename F, typename R, typename A1, typename A2, typename A3 > + void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(A1, A2, A3, std::ostream& out) const ) + { + cmds->push_back(std::make_shared<FuncCmd3<A1, A2, A3>>(name, f, help)); + } + + template < typename F, typename R, typename A1, typename A2, typename A3, typename A4 > + void Menu::Add( const std::string& name, const std::string& help, F& f,R (F::*)(A1, A2, A3, A4, std::ostream& out) const ) + { + cmds->push_back(std::make_shared<FuncCmd4<A1, A2, A3, A4>>(name, f, help)); + } +#endif // CLI_DEPRECATED_API + + template <typename F, typename R, typename ... Args> + CmdHandler Menu::Insert(const std::string& cmdName, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, Args...) const ) + { + return Insert(std::make_unique<VariadicFunctionCommand<F, Args ...>>(cmdName, f, help, parDesc)); + } + + template <typename F, typename R> + CmdHandler Menu::Insert(const std::string& cmdName, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, const std::vector<std::string>& args) const ) + { + return Insert(std::make_unique<FreeformCommand<F>>(cmdName, f, help, parDesc)); + } + + template <typename F, typename R> + CmdHandler Menu::Insert(const std::string& cmdName, const std::string& help, const std::vector<std::string>& parDesc, F& f, R (F::*)(std::ostream& out, std::vector<std::string> args) const ) + { + return Insert(std::make_unique<FreeformCommand<F>>(cmdName, f, help, parDesc)); + } + +} // namespace + +#endif diff --git a/inc/cli/clifilesession.h b/inc/cli/clifilesession.h new file mode 100644 index 00000000..7896c681 --- /dev/null +++ b/inc/cli/clifilesession.h @@ -0,0 +1,83 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_FILESESSION_H_ +#define CLI_FILESESSION_H_ + +#include <string> +#include <iostream> +#include <stdexcept> // std::invalid_argument +#include "cli.h" // CliSession + +namespace cli +{ + +class CliFileSession : public CliSession +{ +public: + /// @throw std::invalid_argument if @c _in or @c out are invalid streams + CliFileSession(Cli& _cli, std::istream& _in=std::cin, std::ostream& _out=std::cout) : + CliSession(_cli, _out, 1), + exit(false), + in(_in) + { + if (!_in.good()) throw std::invalid_argument("istream invalid"); + if (!_out.good()) throw std::invalid_argument("ostream invalid"); + ExitAction( + [this](std::ostream&) + { + exit = true; + } + ); + } + void Start() + { + while(!exit) + { + Prompt(); + std::string line; + if (!in.good()) + Exit(); + std::getline(in, line); + if (in.eof()) + Exit(); + else + Feed(line); + } + } + +private: + bool exit; + std::istream& in; +}; + +} // namespace + +#endif // CLI_FILESESSION_H_ + diff --git a/inc/cli/clilocalsession.h b/inc/cli/clilocalsession.h new file mode 100644 index 00000000..feec7f99 --- /dev/null +++ b/inc/cli/clilocalsession.h @@ -0,0 +1,65 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_LOCALSESSION_H_ +#define CLI_LOCALSESSION_H_ + +#include "detail/keyboard.h" +#include "detail/inputhandler.h" +#include "cli.h" // CliSession + +namespace cli +{ + +class Scheduler; // forward declaration + + +class CliLocalTerminalSession : public CliSession +{ +public: + + CliLocalTerminalSession(Cli& _cli, Scheduler& scheduler, std::ostream& _out, std::size_t historySize = 100) : + CliSession(_cli, _out, historySize), + kb(scheduler), + ih(*this, kb) + { + Prompt(); + } + +private: + detail::Keyboard kb; + detail::InputHandler ih; +}; + +using CliLocalSession = CliLocalTerminalSession; + +} // namespace cli + +#endif // CLI_LOCALSESSION_H_ + diff --git a/inc/cli/colorprofile.h b/inc/cli/colorprofile.h new file mode 100644 index 00000000..0d9714bd --- /dev/null +++ b/inc/cli/colorprofile.h @@ -0,0 +1,76 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_COLORPROFILE_H_ +#define CLI_COLORPROFILE_H_ + +#include "detail/rang.h" + +namespace cli +{ + +inline bool& Color() { static bool color; return color; } + +inline void SetColor() { Color() = true; } +inline void SetNoColor() { Color() = false; } + +enum BeforePrompt { beforePrompt }; +enum AfterPrompt { afterPrompt }; +enum BeforeInput { beforeInput }; +enum AfterInput { afterInput }; + +inline std::ostream& operator<<(std::ostream& os, BeforePrompt) +{ + if ( Color() ) { os << rang::control::forceColor << rang::fg::green << rang::style::bold; } + return os; +} + +inline std::ostream& operator<<(std::ostream& os, AfterPrompt) +{ + os << rang::style::reset; + return os; +} + +inline std::ostream& operator<<(std::ostream& os, BeforeInput) +{ + if ( Color() ) { os << rang::control::forceColor << rang::fgB::gray; } + return os; +} + +inline std::ostream& operator<<(std::ostream& os, AfterInput) +{ + os << rang::style::reset; + return os; +} + +} // namespace cli + +#endif // CLI_COLORPROFILE_H_ + + diff --git a/inc/cli/detail/boostasiolib.h b/inc/cli/detail/boostasiolib.h new file mode 100644 index 00000000..a82c53cd --- /dev/null +++ b/inc/cli/detail/boostasiolib.h @@ -0,0 +1,49 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_BOOSTASIOLIB_H_ +#define CLI_DETAIL_BOOSTASIOLIB_H_ + +/** + * This header file provides the class `cli::BoostAsioLib`, using the right + * implementation according to the version of boost libraries included. + */ + +#include <boost/version.hpp> + +#if BOOST_VERSION < 106600 + #include "oldboostasiolib.h" + namespace cli { namespace detail { using BoostAsioLib = OldBoostAsioLib; } } +#else + #include "newboostasiolib.h" + namespace cli { namespace detail { using BoostAsioLib = NewBoostAsioLib; } } +#endif + +#endif // CLI_DETAIL_BOOSTASIOLIB_H_ + diff --git a/inc/cli/detail/commonprefix.h b/inc/cli/detail/commonprefix.h new file mode 100644 index 00000000..626e089c --- /dev/null +++ b/inc/cli/detail/commonprefix.h @@ -0,0 +1,70 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_COMMONPREFIX_H_ +#define CLI_DETAIL_COMMONPREFIX_H_ + +#include <cassert> +#include <string> +#include <vector> +#include <algorithm> + +namespace cli +{ +namespace detail +{ + +inline std::string CommonPrefix(const std::vector<std::string>& v) +{ + assert(!v.empty()); + std::string prefix; + + // find the shorter string + auto smin = std::min_element(v.begin(), v.end(), + [] (const std::string& s1, const std::string& s2) + { + return s1.size() < s2.size(); + }); + + for (std::size_t i = 0; i < smin->size(); ++i) + { + // check if i-th element is equal in each input string + const char c = (*smin)[i]; + for (auto& x: v) + if (x[i] != c) return prefix; + prefix += c; + } + + return prefix; +} + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_COMMONPREFIX_H_ diff --git a/inc/cli/detail/fromstring.h b/inc/cli/detail/fromstring.h new file mode 100644 index 00000000..822c7de0 --- /dev/null +++ b/inc/cli/detail/fromstring.h @@ -0,0 +1,281 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_FROMSTRING_H_ +#define CLI_DETAIL_FROMSTRING_H_ + +// #define CLI_FROMSTRING_USE_BOOST + +#ifdef CLI_FROMSTRING_USE_BOOST + +#include <boost/lexical_cast.hpp> + +namespace cli +{ +namespace detail +{ + +template <typename T> +inline +T from_string(const std::string& s) +{ + return boost::lexical_cast<T>(s); +} + +} // namespace detail +} // namespace cli + +#else + +#include <exception> +#include <string> +#include <sstream> + +namespace cli +{ + + namespace detail + { + class bad_conversion : public std::bad_cast + { + public: + virtual const char* what() const noexcept { + return "bad from_string conversion: " + "source string value could not be interpreted as target"; + } + }; + +template <typename T> +inline T from_string(const std::string& s); + +template <> +inline std::string from_string(const std::string& s) +{ + return s; +} + +template <> +inline std::nullptr_t from_string(const std::string& /*s*/) +{ + return nullptr; +} + +namespace detail +{ + +template <typename T> +inline T unsigned_digits_from_string(const std::string& s) +{ + if (s.empty()) + throw bad_conversion(); + T result = 0; + for (char c: s) + { + if (!std::isdigit(c)) + throw bad_conversion(); + const T digit = static_cast<T>( c - '0' ); + const T tmp = (result * 10) + digit; + if (result != ((tmp-digit)/10) || (tmp < result)) + throw bad_conversion(); + result = tmp; + } + return result; +} + +template <typename T> +inline T unsigned_from_string(std::string s) +{ + if (s.empty()) + throw bad_conversion(); + if (s[0] == '+') + { + s = s.substr(1); + } + return unsigned_digits_from_string<T>(s); +} + +template <typename T> +inline T signed_from_string(std::string s) +{ + if (s.empty()) + throw bad_conversion(); + using U = std::make_unsigned_t<T>; + if (s[0] == '-') + { + s = s.substr(1); + const U val = unsigned_digits_from_string<U>(s); + if ( val > static_cast<U>( - std::numeric_limits<T>::min() ) ) + throw bad_conversion(); + return (- static_cast<T>(val)); + } + else if (s[0] == '+') + { + s = s.substr(1); + } + const U val = unsigned_digits_from_string<U>(s); + if (val > static_cast<U>( std::numeric_limits<T>::max() )) + throw bad_conversion(); + return static_cast<T>(val); +} + +} // detail + +// signed + +template <> inline signed char +from_string(const std::string& s) { return detail::signed_from_string<signed char>(s); } + +template <> inline short int +from_string(const std::string& s) { return detail::signed_from_string<short int>(s); } + +template <> inline int +from_string(const std::string& s) { return detail::signed_from_string<int>(s); } + +template <> inline long int +from_string(const std::string& s) { return detail::signed_from_string<long int>(s); } + +template <> inline long long int +from_string(const std::string& s) { return detail::signed_from_string<long long int>(s); } + +// unsigned + +template <> inline unsigned char +from_string(const std::string& s) { return detail::unsigned_from_string<unsigned char>(s); } + +template <> inline unsigned short int +from_string(const std::string& s) { return detail::unsigned_from_string<unsigned short int>(s); } + +template <> inline unsigned int +from_string(const std::string& s) { return detail::unsigned_from_string<unsigned int>(s); } + +template <> inline unsigned long int +from_string(const std::string& s) { return detail::unsigned_from_string<unsigned long int>(s); } + +template <> inline unsigned long long int +from_string(const std::string& s) { return detail::unsigned_from_string<unsigned long long int>(s); } + +// bool + +template <> +inline bool from_string(const std::string& s) +{ + if (s == "true") return true; + else if (s == "false") return false; + const auto value = detail::signed_from_string<long long int>(s); + if (value == 1) return true; + else if (value == 0) return false; + throw bad_conversion(); +} + +// chars + +template <> +inline char from_string(const std::string& s) +{ + if (s.size() != 1) throw bad_conversion(); + return s[0]; +} + +// floating points + +template <> +inline float from_string(const std::string& s) +{ + if ( std::any_of(s.begin(), s.end(), [](char c){return std::isspace(c);} ) ) + throw bad_conversion(); + std::string::size_type sz; + float result = {}; + try { + result = std::stof(s, &sz); + } catch (const std::exception&) { + throw bad_conversion(); + } + if (sz != s.size()) + throw bad_conversion(); + return result; +} + +template <> +inline double from_string(const std::string& s) +{ + if ( std::any_of(s.begin(), s.end(), [](char c){return std::isspace(c);} ) ) + throw bad_conversion(); + std::string::size_type sz; + double result = {}; + try { + result = std::stod(s, &sz); + } catch (const std::exception&) { + throw bad_conversion(); + } + if (sz != s.size()) + throw bad_conversion(); + return result; +} + +template <> +inline long double from_string(const std::string& s) +{ + if ( std::any_of(s.begin(), s.end(), [](char c){return std::isspace(c);} ) ) + throw bad_conversion(); + std::string::size_type sz; + long double result = {}; + try { + result = std::stold(s, &sz); + } catch (const std::exception&) { + throw bad_conversion(); + } + if (sz != s.size()) + throw bad_conversion(); + return result; +} + +// fallback: operator << + +template <typename T> +inline T from_string(const std::string& s) +{ + std::stringstream interpreter; + T result; + + if(!(interpreter << s) || + !(interpreter >> result) || + !(interpreter >> std::ws).eof()) + throw bad_conversion(); + + return result; +} + + } // detail + +} // cli + + +#endif // CLI_FROMSTRING_USE_BOOST + +#endif // CLI_DETAIL_FROMSTRING_H_ diff --git a/inc/cli/detail/genericasioremotecli.h b/inc/cli/detail/genericasioremotecli.h new file mode 100644 index 00000000..603bb44a --- /dev/null +++ b/inc/cli/detail/genericasioremotecli.h @@ -0,0 +1,576 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_GENERICASIOREMOTECLI_H_ +#define CLI_DETAIL_GENERICASIOREMOTECLI_H_ + +#include <memory> +#include "../cli.h" +#include "inputhandler.h" +#include "server.h" +#include "inputdevice.h" + +namespace cli +{ +namespace detail +{ + +// ******************************************************************************* + +class TelnetSession : public Session +{ +public: + TelnetSession(asiolib::ip::tcp::socket _socket) : + Session(std::move(_socket)) + {} + +protected: + + virtual std::string Encode(const std::string& _data) const override + { + std::string result; + for (char c: _data) + { + if (c == '\n') result += '\r'; + result += c; + } + return result; + } + + virtual void OnConnect() override + { + // to specify hexadecimal value as chars we use + // the syntax \xVVV + // and the std::string ctor that takes the size, + // so that it's not null-terminated + //std::string msg{ "\x0FF\x0FD\x027", 3 }; + //waitAck = true; + //std::string iacDoSuppressGoAhead{ "\x0FF\x0FD\x003", 3 }; + //this -> OutStream() << iacDoSuppressGoAhead << std::flush; + + // https://www.ibm.com/support/knowledgecenter/SSLTBW_1.13.0/com.ibm.zos.r13.hald001/telcmds.htm + + std::string iacDoLineMode{ "\x0FF\x0FD\x022", 3 }; + this -> OutStream() << iacDoLineMode << std::flush; + + std::string iacSbLineMode0IacSe{ "\x0FF\x0FA\x022\x001\x000\x0FF\x0F0", 7 }; + this -> OutStream() << iacSbLineMode0IacSe << std::flush; + + std::string iacWillEcho{ "\x0FF\x0FB\x001", 3 }; + this -> OutStream() << iacWillEcho << std::flush; +/* + constexpr char IAC = '\x0FF'; // 255 + constexpr char DO = '\x0FD'; // 253 + constexpr char VT100 = '\x030'; // 48 + + this -> OutStream() << IAC << DO << VT100 << std::flush; +*/ + //cliSession.Prompt(); + } + virtual void OnDisconnect() override {} + virtual void OnError() override {} +#if 0 + virtual void OnDataReceived(const std::string& data) override + { + if (waitAck) + { + if ( data[0] == '\x0FF' ) + { + // TODO + for (size_t i = 0; i < data.size(); ++i) + std::cout << static_cast<int>( data[i] & 0xFF ) << ' '; + std::cout << std::endl; + + for (size_t i = 0; i < data.size(); ++i) + std::cout << "0x" << std::hex << static_cast<int>( data[i] & 0xFF ) << std::dec << ' '; + std::cout << std::endl; + + for (size_t i = 0; i < data.size(); ++i) + switch (static_cast<int>( data[i] & 0xFF )) + { + case 0xFF: std::cout << "IAC "; break; + case 0xFE: std::cout << "DONT "; break; + case 0xFD: std::cout << "DO "; break; + case 0xFC: std::cout << "WONT "; break; + case 0xFB: std::cout << "WILL "; break; + case 0xFA: std::cout << "SB "; break; + case 0xF9: std::cout << "GoAhead "; break; + case 0xF8: std::cout << "EraseLine "; break; + case 0xF7: std::cout << "EraseCharacter "; break; + case 0xF6: std::cout << "AreYouThere "; break; + case 0xF5: std::cout << "AbortOutput "; break; + case 0xF4: std::cout << "InterruptProcess "; break; + case 0xF3: std::cout << "Break "; break; + case 0xF2: std::cout << "DataMark "; break; + case 0xF1: std::cout << "NOP "; break; + case 0xF0: std::cout << "SE "; break; + default: std::cout << (static_cast<int>( data[i] & 0xFF )) << ' '; + } + std::cout << std::endl; + + } + waitAck = false; +/* + std::string iacWillSuppressGoAhead{ "\x0FF\x0FB\x003", 3 }; + if ( data == iacWillSuppressGoAhead ) + { + waitAck = false; + cliSession.Prompt(); + } + + else + Disconnect(); +*/ + } + else + { + for (size_t i = 0; i < data.size(); ++i) + Feed(data[i]); +/* + auto str = data; + // trim trailing spaces + std::size_t endpos = str.find_last_not_of(" \t\r\n"); + if( std::string::npos != endpos ) str = str.substr( 0, endpos+1 ); + + if ( cliSession.Feed( str ) ) cliSession.Prompt(); + else Disconnect(); +*/ + } + } +#else + + enum + { + SE = '\x0F0', // End of subnegotiation parameters. + NOP = '\x0F1', // No operation. + DataMark = '\x0F2', // The data stream portion of a Synch. + // This should always be accompanied + // by a TCP Urgent notification. + Break = '\x0F3', // NVT character BRK. + InterruptProcess = '\x0F4', // The function IP. + AbortOutput = '\x0F5', // The function AO. + AreYouThere = '\x0F6', // The function AYT. + EraseCharacter = '\x0F7', // The function EC. + EraseLine = '\x0F8', // The function EL. + GoAhead = '\x0F9', // The GA signal. + SB = '\x0FA', // Indicates that what follows is + // subnegotiation of the indicated + // option. + WILL = '\x0FB', // Indicates the desire to begin + // performing, or confirmation that + // you are now performing, the + // indicated option. + WONT = '\x0FC', // Indicates the refusal to perform, + // or continue performing, the + // indicated option. + DO = '\x0FD', // Indicates the request that the + // other party perform, or + // confirmation that you are expecting + // the other party to perform, the + // indicated option. + DONT = '\x0FE', // Indicates the demand that the + // other party stop performing, + // or confirmation that you are no + // longer expecting the other party + // to perform, the indicated option. + IAC = '\x0FF' // Data Byte 255. + }; + + + virtual void OnDataReceived(const std::string& _data) override + { + for (char c: _data) + Consume(c); + } + +private: + + void Consume(char c) + { + if (escape) + { + if (c == IAC) + { + Data(c); + escape = false; + } + else + { + Command(c); + escape = false; + } + } + else + { + if (c == IAC) + escape = true; + else + Data(c); + } + } + + void Data(char c) + { + switch(state) + { + case State::data: + Output(c); + break; + case State::sub: + Sub(c); + break; + case State::wait_will: + Will(c); + state = State::data; + break; + case State::wait_wont: + Wont(c); + state = State::data; + break; + case State::wait_do: + Do(c); + state = State::data; + break; + case State::wait_dont: + Dont(c); + state = State::data; + break; + } + } + + void Command(char c) + { +/* + switch (static_cast<int>( c & 0xFF )) + { + case 0xFF: std::cout << "IAC" << std::endl; break; + case 0xFE: std::cout << "DONT" << std::endl; break; + case 0xFD: std::cout << "DO" << std::endl; break; + case 0xFC: std::cout << "WONT" << std::endl; break; + case 0xFB: std::cout << "WILL" << std::endl; break; + case 0xFA: std::cout << "SB" << std::endl; break; + case 0xF9: std::cout << "GoAhead" << std::endl; break; + case 0xF8: std::cout << "EraseLine" << std::endl; break; + case 0xF7: std::cout << "EraseCharacter" << std::endl; break; + case 0xF6: std::cout << "AreYouThere" << std::endl; break; + case 0xF5: std::cout << "AbortOutput" << std::endl; break; + case 0xF4: std::cout << "InterruptProcess" << std::endl; break; + case 0xF3: std::cout << "Break" << std::endl; break; + case 0xF2: std::cout << "DataMark" << std::endl; break; + case 0xF1: std::cout << "NOP" << std::endl; break; + case 0xF0: std::cout << "SE" << std::endl; break; + default: std::cout << (static_cast<int>( c & 0xFF )) << std::endl;; + } +*/ + switch(c) + { + case SE: + if (state == State::sub) + state = State::data; + else + std::cout << "ERROR: received SE when not in sub state" << std::endl; + break; + case DataMark: // ? + case Break: // ? + case InterruptProcess: + case AbortOutput: + case AreYouThere: + case EraseCharacter: + case EraseLine: + case GoAhead: + case NOP: + state = State::data; + break; + case SB: + if (state != State::sub) + state = State::sub; + else + std::cout << "ERROR: received SB when already in sub state" << std::endl; + break; + case WILL: + state = State::wait_will; + break; + case WONT: + state = State::wait_wont; + break; + case DO: + state = State::wait_do; + break; + case DONT: + state = State::wait_dont; + break; + case IAC: + assert(false); // can't be here + state = State::data; + break; + } + } + + void Will(char c) + { + #ifdef CLI_TELNET_TRACE + std::cout << "will " << static_cast<int>(c) << std::endl; + #else + (void)c; + #endif + } + void Wont(char c) + { + #ifdef CLI_TELNET_TRACE + std::cout << "wont " << static_cast<int>(c) << std::endl; + #else + (void)c; + #endif + } + void Do(char c) + { + #ifdef CLI_TELNET_TRACE + std::cout << "do " << static_cast<int>(c) << std::endl; + #else + (void)c; + #endif + } + void Dont(char c) + { + #ifdef CLI_TELNET_TRACE + std::cout << "dont " << static_cast<int>(c) << std::endl; + #else + (void)c; + #endif + } + void Sub(char c) + { + #ifdef CLI_TELNET_TRACE + std::cout << "sub: " << static_cast<int>(c) << std::endl; + #else + (void)c; + #endif + } +protected: + virtual void Output(char c) + { + #ifdef CLI_TELNET_TRACE + std::cout << "data: " << static_cast<int>(c) << std::endl; + #else + (void)c; + #endif + } +private: + enum class State { data, sub, wait_will, wait_wont, wait_do, wait_dont }; + State state = State::data; + bool escape = false; + +#endif + +private: + void Feed(char c) + { + if (std::isprint(c)) std::cout << c << std::endl; + else std::cout << "0x" << std::hex << static_cast<int>(c) << std::dec << std::endl; +/* + switch ( c ) + { + case 0: break; + case '\n': + case '\r': + { + // trim trailing spaces + std::size_t endpos = buffer.find_last_not_of(" \t\r\n"); + if( std::string::npos != endpos ) buffer = buffer.substr( 0, endpos+1 ); + if ( cliSession.Feed( buffer ) ) cliSession.Prompt(); + else Disconnect(); + + buffer.clear(); + break; + } + default: + Echo( c ); + buffer += c; + } +*/ + } +/* + void Echo( char c ) + { + this -> OutStream() << c << std::flush; + } +*/ + std::string buffer; + //bool waitAck = false; +}; + +template <typename ASIOLIB> +class TelnetServer : public Server<ASIOLIB> +{ +public: + TelnetServer(typename ASIOLIB::ContextType& ios, unsigned short port) : + Server<ASIOLIB>(ios, port) + {} + virtual std::shared_ptr<Session> CreateSession(asiolib::ip::tcp::socket _socket) override + { + return std::make_shared<TelnetSession>(std::move(_socket)); + } +}; + +////////////// + +class CliTelnetSession : public InputDevice, public TelnetSession, public CliSession +{ +public: + + CliTelnetSession(Scheduler& _scheduler, asiolib::ip::tcp::socket _socket, Cli& _cli, std::function< void(std::ostream&)> _exitAction, std::size_t historySize ) : + InputDevice(_scheduler), + TelnetSession(std::move(_socket)), + CliSession(_cli, TelnetSession::OutStream(), historySize), + poll(*this, *this) + { + ExitAction([this, _exitAction](std::ostream& _out){ _exitAction(_out), Disconnect(); } ); + } +protected: + + virtual void OnConnect() override + { + TelnetSession::OnConnect(); + Prompt(); + } + + void Output(char c) override + { + switch(step) + { + case Step::_1: + switch( c ) + { + case EOF: + case 4: // EOT + Notify(std::make_pair(KeyType::eof,' ')); break; + case 8: // Backspace + case 127: // Backspace or Delete + Notify(std::make_pair(KeyType::backspace, ' ')); break; + //case 10: Notify(std::make_pair(KeyType::ret,' ')); break; + case 27: step = Step::_2; break; // symbol + case 13: step = Step::wait_0; break; // wait for 0 (ENTER key) + default: // ascii + { + const char ch = static_cast<char>(c); + Notify(std::make_pair(KeyType::ascii,ch)); + } + } + break; + + case Step::_2: // got 27 last time + if ( c == 91 ) + { + step = Step::_3; + break; // arrow keys + } + else + { + step = Step::_1; + Notify(std::make_pair(KeyType::ignored,' ')); + break; // unknown + } + break; + + case Step::_3: // got 27 and 91 + switch( c ) + { + case 65: step = Step::_1; Notify(std::make_pair(KeyType::up,' ')); break; + case 66: step = Step::_1; Notify(std::make_pair(KeyType::down,' ')); break; + case 68: step = Step::_1; Notify(std::make_pair(KeyType::left,' ')); break; + case 67: step = Step::_1; Notify(std::make_pair(KeyType::right,' ')); break; + case 70: step = Step::_1; Notify(std::make_pair(KeyType::end,' ')); break; + case 72: step = Step::_1; Notify(std::make_pair(KeyType::home,' ')); break; + default: step = Step::_4; break; // not arrow keys + } + break; + + case Step::_4: + if ( c == 126 ) Notify(std::make_pair(KeyType::canc,' ')); + else Notify(std::make_pair(KeyType::ignored,' ')); + + step = Step::_1; + + break; + + case Step::wait_0: + if ( c == 0 /* linux */ || c == 10 /* win */ ) Notify(std::make_pair(KeyType::ret,' ')); + else Notify(std::make_pair(KeyType::ignored,' ')); + + step = Step::_1; + + break; + + } + } + +private: + + enum class Step { _1, _2, _3, _4, wait_0 }; + Step step = Step::_1; + InputHandler poll; +}; + +template <typename ASIOLIB> +class CliGenericTelnetServer : public Server<ASIOLIB> +{ +public: + CliGenericTelnetServer(GenericAsioScheduler<ASIOLIB>& _scheduler, unsigned short port, Cli& _cli, std::size_t _historySize=100 ) : + Server<ASIOLIB>(_scheduler.AsioContext(), port), + scheduler(_scheduler), + cli(_cli), + historySize(_historySize) + {} + CliGenericTelnetServer(GenericAsioScheduler<ASIOLIB>& _scheduler, std::string address, unsigned short port, Cli& _cli, std::size_t _historySize=100 ) : + Server<ASIOLIB>(_scheduler.AsioContext(), address, port), + scheduler(_scheduler), + cli(_cli), + historySize(_historySize) + {} + void ExitAction( std::function< void(std::ostream&)> action ) + { + exitAction = action; + } + virtual std::shared_ptr<Session> CreateSession(asiolib::ip::tcp::socket _socket) override + { + return std::make_shared<CliTelnetSession>(scheduler, std::move(_socket), cli, exitAction, historySize); + } +private: + Scheduler& scheduler; + Cli& cli; + std::function< void(std::ostream&)> exitAction; + std::size_t historySize; +}; + + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_GENERICASIOREMOTECLI_H_ + diff --git a/inc/cli/detail/genericasioscheduler.h b/inc/cli/detail/genericasioscheduler.h new file mode 100644 index 00000000..8b579612 --- /dev/null +++ b/inc/cli/detail/genericasioscheduler.h @@ -0,0 +1,87 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_GENERICASIOSCHEDULER_H_ +#define CLI_DETAIL_GENERICASIOSCHEDULER_H_ + +#include "../scheduler.h" + +namespace cli +{ +namespace detail +{ + +template <typename ASIOLIB> +class GenericAsioScheduler : public Scheduler +{ +public: + + using ContextType = typename ASIOLIB::ContextType; + + GenericAsioScheduler() : owned{true}, context{new ContextType()}, executor{*context} {} + + GenericAsioScheduler(ContextType& _context) : context{&_context}, executor{*context} {} + + ~GenericAsioScheduler() { if (owned) delete context; } + + // non copyable + GenericAsioScheduler(const GenericAsioScheduler&) = delete; + GenericAsioScheduler& operator=(const GenericAsioScheduler&) = delete; + + void Stop() { context->stop(); } + + void Run() + { + auto work = ASIOLIB::MakeWorkGuard(*context); + context->run(); + } + + void ExecOne() { context->run_one(); } + + void Post(const std::function<void()>& f) override + { + executor.Post(f); + } + + ContextType& AsioContext() { return *context; } + +private: + + using ExecutorType = typename ASIOLIB::Executor; + + bool owned = false; + ContextType* context; + ExecutorType executor; +}; + + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_GENERICASIOSCHEDULER_H_ diff --git a/inc/cli/detail/genericcliasyncsession.h b/inc/cli/detail/genericcliasyncsession.h new file mode 100644 index 00000000..3da81273 --- /dev/null +++ b/inc/cli/detail/genericcliasyncsession.h @@ -0,0 +1,100 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_GENERICCLIASYNCSESSION_H_ +#define CLI_DETAIL_GENERICCLIASYNCSESSION_H_ + +#include <string> +#include "../cli.h" // CliSession +#include "genericasioscheduler.h" + +namespace cli +{ +namespace detail +{ + +template <typename ASIOLIB> +class GenericCliAsyncSession : public CliSession +{ +public: + GenericCliAsyncSession(GenericAsioScheduler<ASIOLIB>& _scheduler, Cli& _cli) : + CliSession(_cli, std::cout, 1), + input(_scheduler.AsioContext(), ::dup(STDIN_FILENO)) + { + Read(); + } + ~GenericCliAsyncSession() + { + input.close(); + } + +private: + + void Read() + { + Prompt(); + // Read a line of input entered by the user. + asiolib::async_read_until( + input, + inputBuffer, + '\n', + std::bind( &GenericCliAsyncSession::NewLine, this, + std::placeholders::_1, + std::placeholders::_2 ) + ); + } + + void NewLine(const asiolibec::error_code& error, std::size_t length ) + { + if ( !error || error == asiolib::error::not_found ) + { + auto bufs = inputBuffer.data(); + auto size = static_cast<long>(length); + if ( !error ) --size; // remove \n + std::string s(asiolib::buffers_begin( bufs ), asiolib::buffers_begin( bufs ) + size); + inputBuffer.consume( length ); + + Feed( s ); + Read(); + } + else + { + input.close(); + } + } + + asiolib::streambuf inputBuffer; + asiolib::posix::stream_descriptor input; +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_GENERICCLIASYNCSESSION_H_ + diff --git a/inc/cli/detail/history.h b/inc/cli/detail/history.h new file mode 100644 index 00000000..d10c7d1a --- /dev/null +++ b/inc/cli/detail/history.h @@ -0,0 +1,164 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_HISTORY_H_ +#define CLI_DETAIL_HISTORY_H_ + +#include <deque> +#include <vector> +#include <string> +#include <algorithm> +#include <cassert> + +namespace cli +{ +namespace detail +{ + +class History +{ +public: + + explicit History(std::size_t size) : maxSize(size) {} + + // Insert a new item in the buffer, changing the current state to "inserting" + // If we're browsing the history (eg with arrow keys) the new item overwrites + // the current one. + // Otherwise, the item is added to the front of the container + void NewCommand(const std::string& item) + { + ++commands; + current = 0; + if (mode == Mode::browsing) + { + assert(!buffer.empty()); + if (buffer.size() > 1 && buffer[1] == item) // try to insert an element identical to last one + buffer.pop_front(); + else // the item was not identical + buffer[current] = item; + } + else // Mode::inserting + { + if (buffer.empty() || buffer[0] != item) // insert an element not equal to last one + Insert(item); + } + mode = Mode::inserting; + } + + // Return the previous item of the history, updating the current item and + // changing the current state to "browsing" + // If we're already browsing the history (eg with arrow keys) the edit line is inserted + // to the front of the container. + // Otherwise, the line overwrites the current item. + std::string Previous(const std::string& line) + { + if (mode == Mode::inserting) + { + Insert(line); + mode = Mode::browsing; + current = (buffer.size() > 1) ? 1 : 0; + } + else // Mode::browsing + { + assert(!buffer.empty()); + buffer[current] = line; + if (current != buffer.size()-1) + ++current; + } + assert(mode == Mode::browsing); + assert(current < buffer.size()); + return buffer[current]; + } + + // Return the next item of the history, updating the current item. + std::string Next() + { + if (buffer.empty() || current == 0) + return {}; + assert(current != 0); + --current; + assert(current < buffer.size()); + return buffer[current]; + } + + // Show the whole history on the given ostream + void Show(std::ostream& out) const + { + out << '\n'; + for (auto& item: buffer) + out << item << '\n'; + out << '\n' << std::flush; + } + + // cmds[0] is the oldest command, cmds[size-1] the newer + void LoadCommands(const std::vector<std::string>& cmds) + { + for (const auto& c: cmds) + Insert(c); + } + + // result[0] is the oldest command, result[size-1] the newer + std::vector<std::string> GetCommands() const + { + auto numCmdsToReturn = std::min(commands, buffer.size()); + auto start = buffer.begin(); + if (mode == Mode::browsing) + { + numCmdsToReturn = std::min(commands, buffer.size()-1); + start = buffer.begin()+1; + } + std::vector<std::string> result(numCmdsToReturn); + assert(std::distance(start, buffer.end()) >= 0); + assert(numCmdsToReturn <= static_cast<std::size_t>(std::distance(buffer.end(), start))); + assert(numCmdsToReturn <= static_cast<unsigned long>(std::numeric_limits<long>::max())); + std::reverse_copy(start, start+static_cast<long>(numCmdsToReturn), result.begin()); + return result; + } + +private: + + void Insert(const std::string& item) + { + buffer.push_front(item); + if (buffer.size() > maxSize) + buffer.pop_back(); + } + + const std::size_t maxSize; + std::deque<std::string> buffer; + std::size_t current = 0; + std::size_t commands = 0; // number of commands issued + enum class Mode { inserting, browsing }; + Mode mode = Mode::inserting; +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_HISTORY_H_ diff --git a/inc/cli/detail/inputdevice.h b/inc/cli/detail/inputdevice.h new file mode 100644 index 00000000..313d79e3 --- /dev/null +++ b/inc/cli/detail/inputdevice.h @@ -0,0 +1,72 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_INPUTDEVICE_H_ +#define CLI_DETAIL_INPUTDEVICE_H_ + +#include <functional> +#include <string> +#include "../scheduler.h" + +namespace cli +{ +namespace detail +{ + +enum class KeyType { ascii, up, down, left, right, backspace, canc, home, end, ret, eof, ignored }; + +class InputDevice +{ +public: + using Handler = std::function< void( std::pair<KeyType,char> ) >; + + InputDevice(Scheduler& _scheduler) : scheduler(_scheduler) {} + virtual ~InputDevice() = default; + + template <typename H> + void Register(H&& h) { handler = std::forward<H>(h); } + +protected: + + void Notify(std::pair<KeyType,char> k) + { + scheduler.Post([this,k](){ if (handler) handler(k); }); + } + +private: + + Scheduler& scheduler; + Handler handler; +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_INPUTDEVICE_H_ + diff --git a/inc/cli/detail/inputhandler.h b/inc/cli/detail/inputhandler.h new file mode 100644 index 00000000..d86ab3d9 --- /dev/null +++ b/inc/cli/detail/inputhandler.h @@ -0,0 +1,133 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_INPUTHANDLER_H_ +#define CLI_DETAIL_INPUTHANDLER_H_ + +#include <functional> +#include <string> +#include "terminal.h" +#include "inputdevice.h" +#include "../cli.h" // CliSession +#include "commonprefix.h" + +namespace cli +{ +namespace detail +{ + +class InputHandler +{ +public: + InputHandler(CliSession& _session, InputDevice& kb) : + session(_session), + terminal(session.OutStream()) + { + kb.Register( [this](auto key){ this->Keypressed(key); } ); + } + +private: + + void Keypressed(std::pair<KeyType, char> k) + { + const std::pair<Symbol,std::string> s = terminal.Keypressed(k); + NewCommand(s); + } + + void NewCommand(const std::pair< Symbol, std::string >& s) + { + switch (s.first) + { + case Symbol::nothing: + { + break; + } + case Symbol::eof: + { + session.Exit(); + break; + } + case Symbol::command: + { + session.Feed(s.second); + session.Prompt(); + break; + } + case Symbol::down: + { + terminal.SetLine(session.NextCmd()); + break; + } + case Symbol::up: + { + auto line = terminal.GetLine(); + terminal.SetLine(session.PreviousCmd(line)); + break; + } + case Symbol::tab: + { + auto line = terminal.GetLine(); + auto completions = session.GetCompletions(line); + + if (completions.empty()) + break; + if (completions.size() == 1) + { + terminal.SetLine(completions[0]+' '); + break; + } + + auto commonPrefix = CommonPrefix(completions); + if (commonPrefix.size() > line.size()) + { + terminal.SetLine(commonPrefix); + break; + } + session.OutStream() << '\n'; + std::string items; + std::for_each( completions.begin(), completions.end(), [&items](auto& cmd){ items += '\t' + cmd; } ); + session.OutStream() << items << '\n'; + session.Prompt(); + terminal.ResetCursor(); + terminal.SetLine( line ); + break; + } + } + + } + + CliSession& session; + Terminal terminal; +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_INPUTHANDLER_H_ + diff --git a/inc/cli/detail/keyboard.h b/inc/cli/detail/keyboard.h new file mode 100644 index 00000000..e300d25a --- /dev/null +++ b/inc/cli/detail/keyboard.h @@ -0,0 +1,58 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_KEYBOARD_H_ +#define CLI_DETAIL_KEYBOARD_H_ + +#if defined(__unix__) || defined(__unix) || defined(__linux__) + #define OS_LINUX +#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64) + #define OS_WIN +#elif defined(__APPLE__) || defined(__MACH__) + #define OS_MAC +#else + #error "Platform not supported (yet)." +#endif + +#if defined(OS_LINUX) || defined(OS_MAC) + #include "linuxkeyboard.h" + namespace cli { namespace detail { using Keyboard = LinuxKeyboard; } } +#elif defined(OS_WIN) + #include "winkeyboard.h" + namespace cli { namespace detail { using Keyboard = WinKeyboard; } } +#else + #error "Platform not supported (yet)." +#endif + +#undef OS_LINUX +#undef OS_WIN +#undef OS_MAC + +#endif // CLI_DETAIL_KEYBOARD_H_ + diff --git a/inc/cli/detail/linuxkeyboard.h b/inc/cli/detail/linuxkeyboard.h new file mode 100644 index 00000000..6dc7743f --- /dev/null +++ b/inc/cli/detail/linuxkeyboard.h @@ -0,0 +1,143 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_LINUXKEYBOARD_H_ +#define CLI_DETAIL_LINUXKEYBOARD_H_ + +#include <thread> +#include <memory> +#include <atomic> + +#include <cstdio> +#include <termios.h> +#include <unistd.h> + +#include "inputdevice.h" + + +namespace cli +{ +namespace detail +{ + +class LinuxKeyboard : public InputDevice +{ +public: + explicit LinuxKeyboard(Scheduler& _scheduler) : + InputDevice(_scheduler) + { + ToManualMode(); + servant = std::make_unique<std::thread>( [this](){ Read(); } ); + servant->detach(); + } + ~LinuxKeyboard() + { + run = false; + ToStandardMode(); + } + +private: + + void Read() + { + while ( run ) + { + auto k = Get(); + Notify(k); + } + } + + std::pair<KeyType,char> Get() + { + int ch = std::getchar(); + switch( ch ) + { + case EOF: + case 4: // EOT + return std::make_pair(KeyType::eof,' '); + break; + case 127: return std::make_pair(KeyType::backspace,' '); break; + case 10: return std::make_pair(KeyType::ret,' '); break; + case 27: // symbol + ch = std::getchar(); + if ( ch == 91 ) // arrow keys + { + ch = std::getchar(); + switch( ch ) + { + case 51: + ch = std::getchar(); + if ( ch == 126 ) return std::make_pair(KeyType::canc,' '); + else return std::make_pair(KeyType::ignored,' '); + break; + case 65: return std::make_pair(KeyType::up,' '); + case 66: return std::make_pair(KeyType::down,' '); + case 68: return std::make_pair(KeyType::left,' '); + case 67: return std::make_pair(KeyType::right,' '); + case 70: return std::make_pair(KeyType::end,' '); + case 72: return std::make_pair(KeyType::home,' '); + default: return std::make_pair(KeyType::ignored,' '); + } + } + break; + default: // ascii + { + const char c = static_cast<char>(ch); + return std::make_pair(KeyType::ascii,c); + } + } + return std::make_pair(KeyType::ignored,' '); + } + + void ToManualMode() + { + constexpr tcflag_t ICANON_FLAG = ICANON; + constexpr tcflag_t ECHO_FLAG = ECHO; + + tcgetattr( STDIN_FILENO, &oldt ); + newt = oldt; + newt.c_lflag &= ~( ICANON_FLAG | ECHO_FLAG ); + tcsetattr( STDIN_FILENO, TCSANOW, &newt ); + } + void ToStandardMode() + { + tcsetattr( STDIN_FILENO, TCSANOW, &oldt ); + } + + termios oldt; + termios newt; + std::atomic<bool> run{ true }; + std::unique_ptr<std::thread> servant; +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_LINUXKEYBOARD_H_ + diff --git a/inc/cli/detail/newboostasiolib.h b/inc/cli/detail/newboostasiolib.h new file mode 100644 index 00000000..c1af8d60 --- /dev/null +++ b/inc/cli/detail/newboostasiolib.h @@ -0,0 +1,83 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_NEWBOOSTASIOLIB_H_ +#define CLI_DETAIL_NEWBOOSTASIOLIB_H_ + +#include <boost/version.hpp> +#include <boost/asio.hpp> + +namespace cli +{ +namespace detail +{ + +namespace asiolib = boost::asio; +namespace asiolibec = boost::system; + +class NewBoostAsioLib +{ +public: + + using ContextType = boost::asio::io_context; + + class Executor + { + public: + explicit Executor(ContextType& ios) : + executor(ios.get_executor()) {} + explicit Executor(boost::asio::ip::tcp::socket& socket) : + executor(socket.get_executor()) {} + template <typename T> void Post(T&& t) { boost::asio::post(executor, std::forward<T>(t)); } + private: +#if BOOST_VERSION >= 107400 + using AsioExecutor = boost::asio::any_io_executor; +#else + using AsioExecutor = boost::asio::executor; +#endif + AsioExecutor executor; + }; + + static boost::asio::ip::address IpAddressFromString(const std::string& address) + { + return boost::asio::ip::make_address(address); + } + + static auto MakeWorkGuard(ContextType& context) + { + return boost::asio::make_work_guard(context); + } + +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_NEWBOOSTASIOLIB_H_ + diff --git a/inc/cli/detail/newstandaloneasiolib.h b/inc/cli/detail/newstandaloneasiolib.h new file mode 100644 index 00000000..eb2174af --- /dev/null +++ b/inc/cli/detail/newstandaloneasiolib.h @@ -0,0 +1,82 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_NEWSTANDALONEASIOLIB_H_ +#define CLI_DETAIL_NEWSTANDALONEASIOLIB_H_ + +#include <asio/version.hpp> +#include <asio.hpp> + +namespace cli +{ +namespace detail +{ + +namespace asiolib = asio; +namespace asiolibec = asio; + +class NewStandaloneAsioLib +{ +public: + + using ContextType = asio::io_context; + + class Executor + { + public: + explicit Executor(ContextType& ios) : + executor(ios.get_executor()) {} + explicit Executor(asio::ip::tcp::socket& socket) : + executor(socket.get_executor()) {} + template <typename T> void Post(T&& t) { asio::post(executor, std::forward<T>(t)); } + private: +#if ASIO_VERSION >= 101700 + using AsioExecutor = asio::any_io_executor; +#else + using AsioExecutor = asio::executor; +#endif + AsioExecutor executor; + }; + + static asio::ip::address IpAddressFromString(const std::string& address) + { + return asio::ip::make_address(address); + } + + static auto MakeWorkGuard(ContextType& context) + { + return asio::make_work_guard(context); + } +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_NEWSTANDALONEASIOLIB_H_ + diff --git a/inc/cli/detail/oldboostasiolib.h b/inc/cli/detail/oldboostasiolib.h new file mode 100644 index 00000000..41c3e154 --- /dev/null +++ b/inc/cli/detail/oldboostasiolib.h @@ -0,0 +1,77 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_OLDBOOSTASIOLIB_H_ +#define CLI_DETAIL_OLDBOOSTASIOLIB_H_ + +#include <boost/asio.hpp> + +namespace cli +{ +namespace detail +{ + +namespace asiolib = boost::asio; +namespace asiolibec = boost::system; + +class OldBoostAsioLib +{ +public: + using ContextType = boost::asio::io_service; + + class Executor + { + public: + explicit Executor(ContextType& _ios) : + ios(_ios) {} + explicit Executor(boost::asio::ip::tcp::socket& socket) : + ios(socket.get_io_service()) {} + template <typename T> void Post(T&& t) { ios.post(std::forward<T>(t)); } + private: + ContextType& ios; + }; + + static boost::asio::ip::address IpAddressFromString(const std::string& address) + { + return boost::asio::ip::address::from_string(address); + } + + static auto MakeWorkGuard(ContextType& context) + { + boost::asio::io_service::work work(context); + return work; + } + +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_OLDBOOSTASIOLIB_H_ + diff --git a/inc/cli/detail/oldstandaloneasiolib.h b/inc/cli/detail/oldstandaloneasiolib.h new file mode 100644 index 00000000..4e8132e7 --- /dev/null +++ b/inc/cli/detail/oldstandaloneasiolib.h @@ -0,0 +1,80 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_OLDSTANDALONEASIOLIB_H_ +#define CLI_DETAIL_OLDSTANDALONEASIOLIB_H_ + +#define ASIO_STANDALONE 1 + +#include <asio.hpp> + +namespace cli +{ +namespace detail +{ + +namespace asiolib = asio; +namespace asiolibec = asio; + +class OldStandaloneAsioLib +{ +public: + + using ContextType = asio::io_service; + + class Executor + { + public: + explicit Executor(ContextType& _ios) : + ios(_ios) {} + explicit Executor(asio::ip::tcp::socket& socket) : + ios(socket.get_io_service()) {} + template <typename T> void Post(T&& t) { ios.post(std::forward<T>(t)); } + private: + ContextType& ios; + }; + + static asio::ip::address IpAddressFromString(const std::string& address) + { + return asio::ip::address::from_string(address); + } + + static auto MakeWorkGuard(ContextType& context) + { + asio::io_service::work work(context); + return work; + } + +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_OLDSTANDALONEASIOLIB_H_ + diff --git a/inc/cli/detail/rang.h b/inc/cli/detail/rang.h new file mode 100644 index 00000000..d5ff96c3 --- /dev/null +++ b/inc/cli/detail/rang.h @@ -0,0 +1,298 @@ +#ifndef RANG_DOT_HPP +#define RANG_DOT_HPP + +#if defined(__unix__) || defined(__unix) || defined(__linux__) +#define OS_LINUX +#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64) +#define OS_WIN +#elif defined(__APPLE__) || defined(__MACH__) +#define OS_MAC +#else +#error Unknown Platform +#endif + +#if defined(OS_LINUX) || defined(OS_MAC) + #include <unistd.h> + #include <cstring> +#elif defined(OS_WIN) + #if !defined(NOMINMAX) + #define NOMINMAX 1 + #endif // !defined(NOMINMAX) + #include <windows.h> + #include <io.h> + #include <VersionHelpers.h> +#endif + +#include <algorithm> +#include <cstdlib> +#include <ios> +#include <iostream> +#include <iterator> +#include <type_traits> + +namespace rang { + +enum class style { + reset = 0, + bold = 1, + dim = 2, + italic = 3, + underline = 4, + blink = 5, + rblink = 6, + reversed = 7, + conceal = 8, + crossed = 9 +}; + +enum class fg { + black = 30, + red = 31, + green = 32, + yellow = 33, + blue = 34, + magenta = 35, + cyan = 36, + gray = 37, + reset = 39 +}; + +enum class bg { + black = 40, + red = 41, + green = 42, + yellow = 43, + blue = 44, + magenta = 45, + cyan = 46, + gray = 47, + reset = 49 +}; + +enum class fgB { + black = 90, + red = 91, + green = 92, + yellow = 93, + blue = 94, + magenta = 95, + cyan = 96, + gray = 97 +}; + +enum class bgB { + black = 100, + red = 101, + green = 102, + yellow = 103, + blue = 104, + magenta = 105, + cyan = 106, + gray = 107 +}; + +enum class control { autoColor = 0, forceColor = 1 }; + + +namespace rang_implementation { + + inline std::streambuf const *&RANG_coutbuf() + { + static std::streambuf const *pOutbuff = std::cout.rdbuf(); + return pOutbuff; + } + + inline std::streambuf const *&RANG_cerrbuf() + { + static std::streambuf const *pErrbuff = std::cerr.rdbuf(); + return pErrbuff; + } + + inline std::streambuf const *&RANG_clogbuf() + { + static std::streambuf const *pLogbuff = std::clog.rdbuf(); + return pLogbuff; + } + + inline int getIword() + { + static int i = std::ios_base::xalloc(); + return i; + } + + + inline bool supportsColor() + { +#if defined(OS_LINUX) || defined(OS_MAC) + static constexpr const char* Terms[] = { + "ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", + "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm" + }; + + const char *env_p = std::getenv("TERM"); + if (env_p == nullptr) { + return false; + } + + static const bool result = std::any_of( + std::begin(Terms), std::end(Terms), [&](const char* term) { + return std::strstr(env_p, term) != nullptr; + }); + +#elif defined(OS_WIN) + static constexpr bool result = true; +#endif + return result; + } + + + inline bool isTerminal(const std::streambuf *osbuf) + { + if (osbuf == RANG_coutbuf()) { +#if defined(OS_LINUX) || defined(OS_MAC) + return isatty(fileno(stdout)) ? true : false; +#elif defined(OS_WIN) + return _isatty(_fileno(stdout)) ? true : false; +#endif + } + + if (osbuf == RANG_cerrbuf() || osbuf == RANG_clogbuf()) { +#if defined(OS_LINUX) || defined(OS_MAC) + return isatty(fileno(stderr)) ? true : false; +#elif defined(OS_WIN) + return _isatty(_fileno(stderr)) ? true : false; +#endif + } + return false; + } + + + template <typename T> + using enableStd = + typename std::enable_if<std::is_same<T, rang::style>::value + || std::is_same<T, rang::fg>::value + || std::is_same<T, rang::bg>::value + || std::is_same<T, rang::fgB>::value + || std::is_same<T, rang::bgB>::value, + std::ostream &>::type; + + +#ifdef OS_WIN + inline HANDLE getVersionDependentHandle() + { + if (IsWindowsVersionOrGreater(10, 0, 0)) return nullptr; + return GetStdHandle(STD_OUTPUT_HANDLE); + } + + inline HANDLE getConsoleHandle() + { + static HANDLE h = getVersionDependentHandle(); + return h; + } + + inline WORD reverseRGB(WORD rgb) + { + static const WORD rev[8] = { 0, 4, 2, 6, 1, 5, 3, 7 }; + return rev[rgb]; + } + + inline void setWinAttribute(rang::bg col, WORD &state) + { + state &= 0xFF0F; + state |= reverseRGB(static_cast<WORD>(col) - 40) << 4; + } + + inline void setWinAttribute(rang::fg col, WORD &state) + { + state &= 0xFFF0; + state |= reverseRGB(static_cast<WORD>(col) - 30); + } + + inline void setWinAttribute(rang::bgB col, WORD &state) + { + state &= 0xFF0F; + state |= (0x8 | reverseRGB(static_cast<WORD>(col) - 100)) << 4; + } + + inline void setWinAttribute(rang::fgB col, WORD &state) + { + state &= 0xFFF0; + state |= (0x8 | reverseRGB(static_cast<WORD>(col) - 90)); + } + + inline void setWinAttribute(rang::style style, WORD &state) + { + if (style == rang::style::reset) { + state = (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); + } + } + + inline WORD ¤t_state() + { + static WORD state + = (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED); + return state; + } + + template <typename T> + inline enableStd<T> setColor(std::ostream &os, T const value) + { + HANDLE h = getConsoleHandle(); + if (h && isTerminal(os.rdbuf())) { + setWinAttribute(value, current_state()); + SetConsoleTextAttribute(h, current_state()); + return os; + } + return os << "\033[" << static_cast<int>(value) << "m"; + } +#else + template <typename T> + inline enableStd<T> setColor(std::ostream &os, T const value) + { + return os << "\033[" << static_cast<int>(value) << "m"; + } +#endif + + template <typename T> + using enableControl = + typename std::enable_if<std::is_same<T, rang::control>::value, + std::ostream &>::type; +} + +inline void init() +{ + rang_implementation::RANG_coutbuf(); + rang_implementation::RANG_cerrbuf(); + rang_implementation::RANG_clogbuf(); +} + +template <typename T> +inline rang_implementation::enableStd<T> operator<<( + std::ostream &os, T const value) +{ + std::streambuf const *osbuf = os.rdbuf(); + return (os.iword(rang_implementation::getIword()) + || ((rang_implementation::supportsColor()) + && (rang_implementation::isTerminal(osbuf)))) + ? rang_implementation::setColor(os, value) + : os; +} + +template <typename T> +inline rang_implementation::enableControl<T> operator<<( + std::ostream &os, T const value) +{ + if (value == rang::control::forceColor) { + os.iword(rang_implementation::getIword()) = 1; + } else if (value == rang::control::autoColor) { + os.iword(rang_implementation::getIword()) = 0; + } + return os; +} +} + +#undef OS_LINUX +#undef OS_WIN +#undef OS_MAC + +#endif /* ifndef RANG_DOT_HPP */ diff --git a/inc/cli/detail/server.h b/inc/cli/detail/server.h new file mode 100644 index 00000000..aad0d7ca --- /dev/null +++ b/inc/cli/detail/server.h @@ -0,0 +1,159 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_SERVER_H_ +#define CLI_DETAIL_SERVER_H_ + +#include <memory> +#include <queue> + +namespace cli +{ +namespace detail +{ + +class Session : public std::enable_shared_from_this<Session>, public std::streambuf +{ +public: + virtual ~Session() = default; + virtual void Start() + { + OnConnect(); + Read(); + } + +protected: + + Session(asiolib::ip::tcp::socket _socket) : socket(std::move(_socket)), outStream( this ) {} + + virtual void Disconnect() + { + socket.shutdown(asiolib::ip::tcp::socket::shutdown_both); + socket.close(); + } + + virtual void Read() + { + auto self( shared_from_this() ); + socket.async_read_some(asiolib::buffer( data, max_length ), + [ this, self ]( asiolibec::error_code ec, std::size_t length ) + { + if ( !socket.is_open() || ( ec == asiolib::error::eof ) || ( ec == asiolib::error::connection_reset ) ) + OnDisconnect(); + else if ( ec ) + OnError(); + else + { + OnDataReceived( std::string( data, length )); + Read(); + } + }); + } + + virtual void Send(const std::string& msg) + { + asiolibec::error_code ec; + asiolib::write(socket, asiolib::buffer(msg), ec); + if ((ec == asiolib::error::eof) || (ec == asiolib::error::connection_reset)) + OnDisconnect(); + else if (ec) + OnError(); + } + + virtual std::ostream& OutStream() { return outStream; } + + virtual void OnConnect() = 0; + virtual void OnDisconnect() = 0; + virtual void OnError() = 0; + virtual void OnDataReceived(const std::string& _data) = 0; + + virtual std::string Encode(const std::string& _data) const { return _data; } + +private: + + // std::streambuf + std::streamsize xsputn( const char* s, std::streamsize n ) override + { + Send(Encode(std::string(s, s+n))); + return n; + } + int overflow( int c ) override + { + Send(Encode(std::string(1, static_cast< char >(c)))); + return c; + } + + asiolib::ip::tcp::socket socket; + enum { max_length = 1024 }; + char data[ max_length ]; + std::ostream outStream; +}; + + +template <typename ASIOLIB> +class Server +{ +public: + // disable value semantics + Server( const Server& ) = delete; + Server& operator = ( const Server& ) = delete; + + Server(typename ASIOLIB::ContextType& ios, unsigned short port) : + acceptor(ios, asiolib::ip::tcp::endpoint(asiolib::ip::tcp::v4(), port)), + socket(ios) + { + Accept(); + } + Server(typename ASIOLIB::ContextType& ios, std::string address, unsigned short port) : + acceptor(ios, asiolib::ip::tcp::endpoint(ASIOLIB::IpAddressFromString(address), port)), + socket(ios) + { + Accept(); + } + virtual ~Server() = default; + // returns shared_ptr instead of unique_ptr because Session needs to use enable_shared_from_this + virtual std::shared_ptr<Session> CreateSession(asiolib::ip::tcp::socket socket) = 0; +private: + void Accept() + { + acceptor.async_accept(socket, [this](asiolibec::error_code ec) + { + if (!ec) CreateSession(std::move(socket))->Start(); + Accept(); + }); + } + asiolib::ip::tcp::acceptor acceptor; + asiolib::ip::tcp::socket socket; +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_SERVER_H_ + diff --git a/inc/cli/detail/split.h b/inc/cli/detail/split.h new file mode 100644 index 00000000..211c5010 --- /dev/null +++ b/inc/cli/detail/split.h @@ -0,0 +1,237 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_SPLIT_H_ +#define CLI_DETAIL_SPLIT_H_ + +#include <string> +#include <vector> +#include <algorithm> + +namespace cli +{ +namespace detail +{ + +class Text +{ +public: + explicit Text(const std::string& _input) : input(_input) + { + } + void SplitInto(std::vector<std::string>& strs) + { + Reset(); + for (char c: input) + Eval(c); + RemoveEmptyEntries(); + splitResult.swap(strs); // puts the result back in strs + } +private: + void Reset() + { + state = State::space; + prev_state = State::space; + sentence_type = SentenceType::double_quote; + splitResult.clear(); + } + + void Eval(char c) + { + switch(state) + { + case State::space: + EvalSpace(c); + break; + case State::word: + EvalWord(c); + break; + case State::sentence: + EvalSentence(c); + break; + case State::escape: + EvalEscape(c); + break; + } + } + + void EvalSpace(char c) + { + if (c == ' ' || c == '\t' || c == '\n') + { + // do nothing + } + else if (c == '"' || c == '\'') + { + NewSentence(c); + } + else if (c == '\\') + { + // This is the case where the first character of a word is escaped. + // Should come back into the word state after this. + prev_state = State::word; + state = State::escape; + splitResult.push_back(""); + } + else + { + state = State::word; + splitResult.push_back(std::string(1, c)); + } + } + + void EvalWord(char c) + { + if (c == ' ' || c == '\t' || c == '\n') + { + state = State::space; + } + else if (c == '"' || c == '\'') + { + NewSentence(c); + } + else if (c == '\\') + { + prev_state = state; + state = State::escape; + } + else + { + assert(!splitResult.empty()); + splitResult.back() += c; + } + } + + void EvalSentence(char c) + { + if (c == '"' || c == '\'') + { + auto new_type = c == '"' ? SentenceType::double_quote : SentenceType::quote; + if (new_type == sentence_type) + state = State::space; + else + { + assert(!splitResult.empty()); + splitResult.back() += c; + } + } + else if (c == '\\') + { + prev_state = state; + state = State::escape; + } + else + { + assert(!splitResult.empty()); + splitResult.back() += c; + } + } + + void EvalEscape(char c) + { + assert(!splitResult.empty()); + if (c != '"' && c != '\'' && c != '\\') + splitResult.back() += "\\"; + splitResult.back() += c; + state = prev_state; + } + + void NewSentence(char c) + { + state = State::sentence; + sentence_type = ( c == '"' ? SentenceType::double_quote : SentenceType::quote); + splitResult.push_back(""); + } + + void RemoveEmptyEntries() + { + // remove null entries from the vector: + splitResult.erase( + std::remove_if( + splitResult.begin(), + splitResult.end(), + [](const std::string& s){ return s.empty(); } + ), + splitResult.end() + ); + } + + enum class State { space, word, sentence, escape }; + enum class SentenceType { quote, double_quote }; + State state = State::space; + State prev_state = State::space; + SentenceType sentence_type = SentenceType::double_quote; + const std::string input; + std::vector<std::string> splitResult; +}; + +// Split the string input into a vector of strings. +// The original string is split where there are spaces. +// Quotes and double quotes can be used to indicate a substring that should not be splitted +// (even if it contains spaces) + +// split(strs, ""); => empty vector +// split(strs, " "); => empty vector +// split(strs, " "); => empty vector +// split(strs, "\t"); => empty vector +// split(strs, " \t \t "); => empty vector + +// split(strs, "1234567890"); => <"1234567890"> +// split(strs, " foo "); => <"foo"> +// split(strs, " foo \t \t bar \t"); => <"foo","bar"> + +// split(strs, "\"\""); => empty vector +// split(strs, "\"foo bar\""); => <"foo bar"> +// split(strs, " \t\t \"foo \tbar\" \t"); => <"foo \tbar"> +// split(strs, " first \t\t \"foo \tbar\" \t last"); => <"first","foo \tbar","last"> +// split(strs, "first\"foo \tbar\""); => <"first","foo \tbar"> +// split(strs, "first \"'second' 'thirdh'\""); => <"first","'second' 'thirdh'"> + +// split(strs, "''"); => empty vector +// split(strs, "'foo bar'"); => <"foo bar"> +// split(strs, " \t\t 'foo \tbar' \t"); => <"foo \tbar"> +// split(strs, " first \t\t 'foo \tbar' \t last"); => <"first","foo \tbar","last"> +// split(strs, "first'foo \tbar'"); => <"first","foo \tbar"> +// split(strs, "first '\"second\" \"thirdh\"'"); => <"first","\"second\" \"thirdh\""> + +// split(strs, R"("foo\"bar")"); // "foo\"bar" => <"foo"bar"> +// split(strs, R"('foo\'bar')"); // 'foo\'bar' => <"foo'bar"> +// split(strs, R"("foo\bar")"); // "foo\bar" => <"foo\bar"> +// split(strs, R"("foo\\"bar")"); // "foo\\"bar" => <"foo\"bar"> + +inline void split(std::vector<std::string>& strs, const std::string& input) +{ + Text sentence(input); + sentence.SplitInto(strs); +} + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_SPLIT_H_ diff --git a/inc/cli/detail/standaloneasiolib.h b/inc/cli/detail/standaloneasiolib.h new file mode 100644 index 00000000..adb747ea --- /dev/null +++ b/inc/cli/detail/standaloneasiolib.h @@ -0,0 +1,49 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_STANDALONEASIOLIB_H_ +#define CLI_DETAIL_STANDALONEASIOLIB_H_ + +/** + * This header file provides the class `cli::StandaloneAsioLib`, using the right + * implementation according to the version of asio libraries included. + */ + +#include <asio/version.hpp> + +#if ASIO_VERSION < 101300 + #include "oldstandaloneasiolib.h" + namespace cli { namespace detail { using StandaloneAsioLib = OldStandaloneAsioLib; } } +#else + #include "newstandaloneasiolib.h" + namespace cli { namespace detail { using StandaloneAsioLib = NewStandaloneAsioLib; } } +#endif + +#endif // CLI_DETAIL_STANDALONEASIOLIB_H_ + diff --git a/inc/cli/detail/terminal.h b/inc/cli/detail/terminal.h new file mode 100644 index 00000000..a21b8ac4 --- /dev/null +++ b/inc/cli/detail/terminal.h @@ -0,0 +1,213 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_TERMINAL_H_ +#define CLI_DETAIL_TERMINAL_H_ + +#include <string> +#include "../colorprofile.h" +#include "inputdevice.h" + +namespace cli +{ +namespace detail +{ + +enum class Symbol +{ + nothing, + command, + up, + down, + tab, + eof +}; + +class Terminal +{ + public: + Terminal(std::ostream &_out) : out(_out) {} + + void ResetCursor() { position = 0; } + + void SetLine(const std::string &newLine) + { + out << beforeInput + << std::string(position, '\b') << newLine + << afterInput << std::flush; + + // if newLine is shorter then currentLine, we have + // to clear the rest of the string + if (newLine.size() < currentLine.size()) + { + out << std::string(currentLine.size() - newLine.size(), ' '); + // and go back + out << std::string(currentLine.size() - newLine.size(), '\b') << std::flush; + } + + currentLine = newLine; + position = currentLine.size(); + } + + std::string GetLine() const { return currentLine; } + + std::pair<Symbol, std::string> Keypressed(std::pair<KeyType, char> k) + { + switch (k.first) + { + case KeyType::eof: + return std::make_pair(Symbol::eof, std::string{}); + break; + case KeyType::backspace: + { + if (position == 0) + break; + + --position; + + const auto pos = static_cast<std::string::difference_type>(position); + // remove the char from buffer + currentLine.erase(currentLine.begin() + pos); + // go back to the previous char + out << '\b'; + // output the rest of the line + out << std::string(currentLine.begin() + pos, currentLine.end()); + // remove last char + out << ' '; + // go back to the original position + out << std::string(currentLine.size() - position + 1, '\b') << std::flush; + break; + } + case KeyType::up: + return std::make_pair(Symbol::up, std::string{}); + break; + case KeyType::down: + return std::make_pair(Symbol::down, std::string{}); + break; + case KeyType::left: + if (position > 0) + { + out << '\b' << std::flush; + --position; + } + break; + case KeyType::right: + if (position < currentLine.size()) + { + out << beforeInput + << currentLine[position] + << afterInput << std::flush; + ++position; + } + break; + case KeyType::ret: + { + out << "\r\n"; + auto cmd = currentLine; + currentLine.clear(); + position = 0; + return std::make_pair(Symbol::command, cmd); + } + break; + case KeyType::ascii: + { + const char c = static_cast<char>(k.second); + if (c == '\t') + return std::make_pair(Symbol::tab, std::string()); + else + { + const auto pos = static_cast<std::string::difference_type>(position); + + // output the new char: + out << beforeInput << c; + // and the rest of the string: + out << std::string(currentLine.begin() + pos, currentLine.end()) + << afterInput; + + // go back to the original position + out << std::string(currentLine.size() - position, '\b') << std::flush; + + // update the buffer and cursor position: + currentLine.insert(currentLine.begin() + pos, c); + ++position; + } + + break; + } + case KeyType::canc: + { + if (position == currentLine.size()) + break; + + const auto pos = static_cast<std::string::difference_type>(position); + + // output the rest of the line + out << std::string(currentLine.begin() + pos + 1, currentLine.end()); + // remove last char + out << ' '; + // go back to the original position + out << std::string(currentLine.size() - position, '\b') << std::flush; + // remove the char from buffer + currentLine.erase(currentLine.begin() + pos); + break; + } + case KeyType::end: + { + const auto pos = static_cast<std::string::difference_type>(position); + + out << beforeInput + << std::string(currentLine.begin() + pos, currentLine.end()) + << afterInput << std::flush; + position = currentLine.size(); + break; + } + case KeyType::home: + { + out << std::string(position, '\b') << std::flush; + position = 0; + break; + } + case KeyType::ignored: + // TODO + break; + } + + return std::make_pair(Symbol::nothing, std::string()); + } + + private: + std::string currentLine; + std::size_t position = 0; // next writing position in currentLine + std::ostream &out; +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_TERMINAL_H_ diff --git a/inc/cli/detail/winkeyboard.h b/inc/cli/detail/winkeyboard.h new file mode 100644 index 00000000..bee9a0df --- /dev/null +++ b/inc/cli/detail/winkeyboard.h @@ -0,0 +1,132 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_DETAIL_WINKEYBOARD_H_ +#define CLI_DETAIL_WINKEYBOARD_H_ + +#include <functional> +#include <string> +#include <thread> +#include <memory> +#include <atomic> +#include <conio.h> + +#include "inputdevice.h" + +namespace cli +{ +namespace detail +{ + +class WinKeyboard : public InputDevice +{ +public: + explicit WinKeyboard(Scheduler& _scheduler) : InputDevice(_scheduler) + { + servant = std::make_unique<std::thread>( [this](){ Read(); } ); + servant->detach(); + } + ~WinKeyboard() + { + run = false; + } + +private: + void Read() + { + while (run) + { + auto k = Get(); + Notify(k); + } + } + + std::pair<KeyType, char> Get() + { + int c = _getch(); + switch (c) + { + case EOF: + case 4: // EOT ie CTRL-D + case 26: // CTRL-Z + case 3: // CTRL-C + return std::make_pair(KeyType::eof, ' '); + break; + + case 224: // symbol + { + c = _getch(); + switch (c) + { + case 72: + return std::make_pair(KeyType::up, ' '); + break; + case 80: + return std::make_pair(KeyType::down, ' '); + break; + case 75: + return std::make_pair(KeyType::left, ' '); + break; + case 77: + return std::make_pair(KeyType::right, ' '); + break; + case 71: + return std::make_pair(KeyType::home, ' '); + break; + case 79: + return std::make_pair(KeyType::end, ' '); + break; + case 83: + return std::make_pair(KeyType::canc, ' '); + break; + } + } + case 8: + return std::make_pair(KeyType::backspace, c); + break; + case 13: + return std::make_pair(KeyType::ret, c); + break; + default: // hopefully ascii + { + const char ch = static_cast<char>(c); + return std::make_pair(KeyType::ascii, ch); + } + } + return std::make_pair(KeyType::ignored, ' '); + } + + std::atomic<bool> run{true}; + std::unique_ptr<std::thread> servant; +}; + +} // namespace detail +} // namespace cli + +#endif // CLI_DETAIL_WINKEYBOARD_H_ diff --git a/inc/cli/filehistorystorage.h b/inc/cli/filehistorystorage.h new file mode 100644 index 00000000..19f01ba6 --- /dev/null +++ b/inc/cli/filehistorystorage.h @@ -0,0 +1,85 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_FILEHISTORYSTORAGE_H_ +#define CLI_FILEHISTORYSTORAGE_H_ + +#include "historystorage.h" +#include <fstream> + +namespace cli +{ + +class FileHistoryStorage : public HistoryStorage +{ +public: + FileHistoryStorage(const std::string& _fileName, std::size_t size = 1000) : + maxSize(size), + fileName(_fileName) + { + } + void Store(const std::vector<std::string>& cmds) override + { + using dt = std::vector<std::string>::difference_type; + auto commands = Commands(); + commands.insert(commands.end(), cmds.begin(), cmds.end()); + if (commands.size() > maxSize) + commands.erase( + commands.begin(), + commands.begin() + static_cast<dt>(commands.size() - maxSize) + ); + std::ofstream f(fileName, std::ios_base::out); + for (const auto& line: commands) + f << line << '\n'; + } + std::vector<std::string> Commands() const override + { + std::vector<std::string> commands; + std::ifstream in(fileName); + if (in) + { + std::string line; + while (std::getline(in, line)) + commands.push_back(line); + } + return commands; + } + void Clear() override + { + std::ofstream f(fileName, std::ios_base::out | std::ios_base::trunc); + } + +private: + const std::size_t maxSize; + const std::string fileName; +}; + +} // namespace cli + +#endif // CLI_FILEHISTORYSTORAGE_H_ diff --git a/inc/cli/historystorage.h b/inc/cli/historystorage.h new file mode 100644 index 00000000..f5af2b14 --- /dev/null +++ b/inc/cli/historystorage.h @@ -0,0 +1,54 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_HISTORYSTORAGE_H_ +#define CLI_HISTORYSTORAGE_H_ + +#include <vector> +#include <string> + +namespace cli +{ + +class HistoryStorage +{ +public: + virtual ~HistoryStorage() = default; + // Store a vector of commands in the history storage + virtual void Store(const std::vector<std::string>& commands) = 0; + // Returns all the commands stored + virtual std::vector<std::string> Commands() const = 0; + // Clear the whole content of the storage + // After calling this method, Commands() returns the empty vector + virtual void Clear() = 0; +}; + +} // namespace cli + +#endif // CLI_HISTORYSTORAGE_H_ diff --git a/inc/cli/loopscheduler.h b/inc/cli/loopscheduler.h new file mode 100644 index 00000000..e3f65447 --- /dev/null +++ b/inc/cli/loopscheduler.h @@ -0,0 +1,103 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_LOOPSCHEDULER_H_ +#define CLI_LOOPSCHEDULER_H_ + +#include <queue> +#include <thread> +#include <mutex> +#include <condition_variable> +#include "scheduler.h" + +namespace cli +{ + +/** + * @brief The LoopScheduler is a simple thread-safe scheduler + * + */ +class LoopScheduler : public Scheduler +{ +public: + LoopScheduler() = default; + ~LoopScheduler() + { + Stop(); + } + + // non copyable + LoopScheduler(const LoopScheduler&) = delete; + LoopScheduler& operator=(const LoopScheduler&) = delete; + + void Stop() + { + std::lock_guard<std::mutex> lck (mtx); + running = false; + cv.notify_all(); + } + void Run() + { + while( ExecOne() ) {}; + } + + void Post(const std::function<void()>& f) override + { + std::lock_guard<std::mutex> lck (mtx); + tasks.push(f); + cv.notify_all(); + } + + bool ExecOne() + { + std::function<void()> task; + { + std::unique_lock<std::mutex> lck(mtx); + cv.wait(lck, [this](){ return !running || !tasks.empty(); }); + if (!running) + return false; + task = tasks.front(); + tasks.pop(); + } + + if (task) + task(); + + return true; + } +private: + std::queue<std::function<void()>> tasks; + bool running{ true }; + std::mutex mtx; + std::condition_variable cv; +}; + +} // namespace cli + +#endif // CLI_LOOPSCHEDULER_H_ diff --git a/inc/cli/scheduler.h b/inc/cli/scheduler.h new file mode 100644 index 00000000..6c74ec85 --- /dev/null +++ b/inc/cli/scheduler.h @@ -0,0 +1,55 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_SCHEDULER_H_ +#define CLI_SCHEDULER_H_ + +#include <functional> + +namespace cli +{ + +/** + * A `Scheduler` represents an engine capable of running a task. + * Its method `Post` can be safely called from any thread to submit the task + * that will execute in an unspecified thread of execution as soon as possible + * (but in any case after the call to `Post` is terminated). + */ +class Scheduler +{ +public: + virtual ~Scheduler() = default; + + /// Submits a completion token or function object for execution. + virtual void Post(const std::function<void()>& f) = 0; +}; + +} // namespace cli + +#endif // CLI_SCHEDULER_H_ diff --git a/inc/cli/standaloneasiocliasyncsession.h b/inc/cli/standaloneasiocliasyncsession.h new file mode 100644 index 00000000..c5eb6809 --- /dev/null +++ b/inc/cli/standaloneasiocliasyncsession.h @@ -0,0 +1,40 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_STANDALONEASIOCLIASYNCSESSION_H_ +#define CLI_STANDALONEASIOCLIASYNCSESSION_H_ + +#include "detail/genericcliasyncsession.h" +#include "detail/standaloneasiolib.h" + + +namespace cli { using StandaloneAsioCliAsyncSession = detail::GenericCliAsyncSession<detail::StandaloneAsioLib>; } + +#endif // CLI_STANDALONEASIOCLIASYNCSESSION_H_ + diff --git a/inc/cli/standaloneasioremotecli.h b/inc/cli/standaloneasioremotecli.h new file mode 100644 index 00000000..140623bf --- /dev/null +++ b/inc/cli/standaloneasioremotecli.h @@ -0,0 +1,40 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_STANDALONEASIOREMOTECLI_H_ +#define CLI_STANDALONEASIOREMOTECLI_H_ + +#include "detail/genericasioremotecli.h" +#include "detail/standaloneasiolib.h" + +namespace cli { using StandaloneAsioCliTelnetServer = detail::CliGenericTelnetServer<detail::StandaloneAsioLib>; } + + +#endif // CLI_STANDALONEASIOREMOTECLI_H_ + diff --git a/inc/cli/standaloneasioscheduler.h b/inc/cli/standaloneasioscheduler.h new file mode 100644 index 00000000..fade453f --- /dev/null +++ b/inc/cli/standaloneasioscheduler.h @@ -0,0 +1,38 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_STANDALONEASIOSCHEDULER_H_ +#define CLI_STANDALONEASIOSCHEDULER_H_ + +#include "detail/genericasioscheduler.h" +#include "detail/standaloneasiolib.h" + +namespace cli { using StandaloneAsioScheduler = detail::GenericAsioScheduler<detail::StandaloneAsioLib>; } + +#endif // CLI_STANDALONEASIOSCHEDULER_H_ diff --git a/inc/cli/volatilehistorystorage.h b/inc/cli/volatilehistorystorage.h new file mode 100644 index 00000000..d357c011 --- /dev/null +++ b/inc/cli/volatilehistorystorage.h @@ -0,0 +1,68 @@ +/******************************************************************************* + * CLI - A simple command line interface. + * Copyright (C) 2016-2021 Daniele Pallastrelli + * + * Boost Software License - Version 1.0 - August 17th, 2003 + * + * Permission is hereby granted, free of charge, to any person or organization + * obtaining a copy of the software and accompanying documentation covered by + * this license (the "Software") to use, reproduce, display, distribute, + * execute, and transmit the Software, and to prepare derivative works of the + * Software, and to permit third-parties to whom the Software is furnished to + * do so, all subject to the following: + * + * The copyright notices in the Software and this entire statement, including + * the above license grant, this restriction and the following disclaimer, + * must be included in all copies of the Software, in whole or in part, and + * all derivative works of the Software, unless such copies or derivative + * works are solely in the form of machine-executable object code generated by + * a source language processor. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + * SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + * FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ******************************************************************************/ + +#ifndef CLI_VOLATILEHISTORYSTORAGE_H_ +#define CLI_VOLATILEHISTORYSTORAGE_H_ + +#include "historystorage.h" +#include <deque> + +namespace cli +{ + +class VolatileHistoryStorage : public HistoryStorage +{ + public: + explicit VolatileHistoryStorage(std::size_t size = 1000) : maxSize(size) {} + void Store(const std::vector<std::string>& cmds) override + { + using dt = std::deque<std::string>::difference_type; + commands.insert(commands.end(), cmds.begin(), cmds.end()); + if (commands.size() > maxSize) + commands.erase( + commands.begin(), + commands.begin()+static_cast<dt>(commands.size()-maxSize) + ); + } + std::vector<std::string> Commands() const override + { + return std::vector<std::string>(commands.begin(), commands.end()); + } + void Clear() override + { + commands.clear(); + } + private: + const std::size_t maxSize; + std::deque<std::string> commands; +}; + +} // namespace cli + +#endif // CLI_VOLATILEHISTORYSTORAGE_H_ diff --git a/src/Makefile.am b/src/Makefile.am index 6d4b9724..b6aad01c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -84,12 +84,16 @@ EXTRA_DIST = database/disk.sql database/memory.sql ## MAIN FILES ## ################ -bin_PROGRAMS = lektord lkt +bin_PROGRAMS = lektord lkt luka ## The lkt client lkt_SOURCES = main/lkt.c base/cmd.c base/common.c base/segv.c lkt_LDFLAGS = -pthread -ldl -static +## The luka client +luka_SOURCES = main/luka.cpp +luka_LDFLAGS = -pthread -static + ## The lektord server lektord_SOURCES = main/server.c lektord_LDADD = liblektor.la diff --git a/src/Makefile.in b/src/Makefile.in index b4ae1150..a7164527 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -134,7 +134,8 @@ host_triplet = @host@ # TODO pour le Kubat du futur: c'est pas beau, trouver un truc pour que ça se fasse tout seul @LKT_STATIC_MODULE_TRUE@am__append_8 = module/thread.c -bin_PROGRAMS = lektord$(EXEEXT) lkt$(EXEEXT) $(am__EXEEXT_1) +bin_PROGRAMS = lektord$(EXEEXT) lkt$(EXEEXT) luka$(EXEEXT) \ + $(am__EXEEXT_1) @LKT_KLKT_TRUE@am__append_9 = klkt/klkt @LKT_KLKT_TRUE@am__append_10 = $(BUILT_SOURCES) @LKT_KLKT_TRUE@am__append_11 = $(BUILT_SOURCES) @@ -268,6 +269,12 @@ lkt_LDADD = $(LDADD) lkt_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ $(lkt_LDFLAGS) $(LDFLAGS) -o $@ +am_luka_OBJECTS = main/luka.$(OBJEXT) +luka_OBJECTS = $(am_luka_OBJECTS) +luka_LDADD = $(LDADD) +luka_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CXX $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(luka_LDFLAGS) $(LDFLAGS) -o $@ AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) am__v_P_0 = false @@ -306,7 +313,8 @@ am__depfiles_remade = base/$(DEPDIR)/cmd.Po base/$(DEPDIR)/common.Po \ database/$(DEPDIR)/liblektor_la-user.Plo \ klkt/$(DEPDIR)/klkt-icons.qrc.Po klkt/$(DEPDIR)/klkt-klkt.Po \ klkt/$(DEPDIR)/klkt-klkt.moc.Po main/$(DEPDIR)/lkt.Po \ - main/$(DEPDIR)/server.Po mkv/$(DEPDIR)/liblektor_la-mkv.Plo \ + main/$(DEPDIR)/luka.Po main/$(DEPDIR)/server.Po \ + mkv/$(DEPDIR)/liblektor_la-mkv.Plo \ mkv/$(DEPDIR)/liblektor_la-utils.Plo \ mkv/$(DEPDIR)/liblektor_la-write.Plo \ module/$(DEPDIR)/liblektor_la-thread.Plo \ @@ -356,11 +364,11 @@ am__v_CXXLD_0 = @echo " CXXLD " $@; am__v_CXXLD_1 = SOURCES = $(liblektor_la_SOURCES) $(liblktmodrepo_la_SOURCES) \ $(liblktmodsdl_la_SOURCES) $(klkt_klkt_SOURCES) \ - $(lektord_SOURCES) $(lkt_SOURCES) + $(lektord_SOURCES) $(lkt_SOURCES) $(luka_SOURCES) DIST_SOURCES = $(am__liblektor_la_SOURCES_DIST) \ $(liblktmodrepo_la_SOURCES) $(liblktmodsdl_la_SOURCES) \ $(am__klkt_klkt_SOURCES_DIST) $(lektord_SOURCES) \ - $(lkt_SOURCES) + $(lkt_SOURCES) $(luka_SOURCES) am__can_run_installinfo = \ case $$AM_UPDATE_INFO_DIR in \ n|no|NO) false;; \ @@ -593,6 +601,8 @@ CLEANFILES = database/disk.c database/memory.c $(am__append_11) EXTRA_DIST = database/disk.sql database/memory.sql $(am__append_10) lkt_SOURCES = main/lkt.c base/cmd.c base/common.c base/segv.c lkt_LDFLAGS = -pthread -ldl -static +luka_SOURCES = main/luka.cpp +luka_LDFLAGS = -pthread -static lektord_SOURCES = main/server.c lektord_LDADD = liblektor.la lektord_LDFLAGS = -static @@ -898,6 +908,12 @@ base/segv.$(OBJEXT): base/$(am__dirstamp) \ lkt$(EXEEXT): $(lkt_OBJECTS) $(lkt_DEPENDENCIES) $(EXTRA_lkt_DEPENDENCIES) @rm -f lkt$(EXEEXT) $(AM_V_CCLD)$(lkt_LINK) $(lkt_OBJECTS) $(lkt_LDADD) $(LIBS) +main/luka.$(OBJEXT): main/$(am__dirstamp) \ + main/$(DEPDIR)/$(am__dirstamp) + +luka$(EXEEXT): $(luka_OBJECTS) $(luka_DEPENDENCIES) $(EXTRA_luka_DEPENDENCIES) + @rm -f luka$(EXEEXT) + $(AM_V_CXXLD)$(luka_LINK) $(luka_OBJECTS) $(luka_LDADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) @@ -944,6 +960,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@klkt/$(DEPDIR)/klkt-klkt.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@klkt/$(DEPDIR)/klkt-klkt.moc.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@main/$(DEPDIR)/lkt.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@main/$(DEPDIR)/luka.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@main/$(DEPDIR)/server.Po@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mkv/$(DEPDIR)/liblektor_la-mkv.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@mkv/$(DEPDIR)/liblektor_la-utils.Plo@am__quote@ # am--include-marker @@ -1458,6 +1475,7 @@ distclean: distclean-am -rm -f klkt/$(DEPDIR)/klkt-klkt.Po -rm -f klkt/$(DEPDIR)/klkt-klkt.moc.Po -rm -f main/$(DEPDIR)/lkt.Po + -rm -f main/$(DEPDIR)/luka.Po -rm -f main/$(DEPDIR)/server.Po -rm -f mkv/$(DEPDIR)/liblektor_la-mkv.Plo -rm -f mkv/$(DEPDIR)/liblektor_la-utils.Plo @@ -1542,6 +1560,7 @@ maintainer-clean: maintainer-clean-am -rm -f klkt/$(DEPDIR)/klkt-klkt.Po -rm -f klkt/$(DEPDIR)/klkt-klkt.moc.Po -rm -f main/$(DEPDIR)/lkt.Po + -rm -f main/$(DEPDIR)/luka.Po -rm -f main/$(DEPDIR)/server.Po -rm -f mkv/$(DEPDIR)/liblektor_la-mkv.Plo -rm -f mkv/$(DEPDIR)/liblektor_la-utils.Plo diff --git a/src/main/luka.cpp b/src/main/luka.cpp new file mode 100644 index 00000000..48f6d820 --- /dev/null +++ b/src/main/luka.cpp @@ -0,0 +1,33 @@ +#include <cli/cli.h> +#include <cli/clifilesession.h> + +#include <iostream> + +int main() +{ + cli::CmdHandler colorCmd; + cli::CmdHandler nocolorCmd; + + auto rootMenu = std::make_unique<cli::Menu>("luka"); + rootMenu->Insert("toto", [](std::ostream &out) noexcept { + out << "This is the toto command for the luka client\n"; + }); + rootMenu->Insert("color", [](std::ostream &out) noexcept { + out << "Enabling colors\n"; + cli::SetColor(); + }, "Enable colors in output"); + rootMenu->Insert("nocolor", [](std::ostream &out) noexcept { + out << "Enabling colors\n"; + cli::SetNoColor(); + }, "Disable colors in output"); + + cli::Cli cli(std::move(rootMenu)); + cli.ExitAction([] (auto &out) noexcept { out << "The luka client is quitting!\n"; }); + cli.StdExceptionHandler([](auto& out, const auto& cmd, const std::exception& e) noexcept { + out << "Luka caught an exception while handling the command " + << cmd << " : " << e.what() << "\n"; + }); + + cli::CliFileSession input(cli); + input.Start(); +} diff --git a/utils/licenses/CLI.LICENSE b/utils/licenses/CLI.LICENSE new file mode 100644 index 00000000..53f65857 --- /dev/null +++ b/utils/licenses/CLI.LICENSE @@ -0,0 +1,28 @@ +The luka client uses the cli header library, that library is distributed with +the Boost Softaware License version 1.0 that can be seen below. + +--- + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. -- GitLab