From fd1c1d3b31272c2b8e41ba0ee5c91a93ae36f446 Mon Sep 17 00:00:00 2001 From: Rainer Schoenberger Date: Thu, 7 Mar 2019 11:32:47 +0100 Subject: [PATCH] move gWhisper code to the open-source repository --- .gitignore | 3 + .gitmodules | 3 + CMakeLists.txt | 55 + CONTRIBUTING.md | 151 +++ DCO1.1.txt | 25 + MAINTAINERS.md | 6 + PROJECT_SCOPE.md | 59 + README.md | 114 +- build.sh | 26 + complete.bash | 70 ++ doc/Usage.txt | 88 ++ example.gif | Bin 0 -> 362359 bytes src/CMakeLists.txt | 27 + src/generateVersionDefine.sh | 55 + src/gwhisper/CMakeLists.txt | 35 + src/gwhisper/generateRawStringFile.sh | 22 + src/gwhisper/gwhisper.cpp | 109 ++ src/libArgParse/Alternation.hpp | 158 +++ src/libArgParse/ArgParse.cpp | 106 ++ src/libArgParse/ArgParse.hpp | 57 + src/libArgParse/ArgParseUtils.hpp | 67 ++ src/libArgParse/CMakeLists.txt | 27 + src/libArgParse/Concatenation.hpp | 164 +++ src/libArgParse/FixedString.hpp | 86 ++ src/libArgParse/Grammar.hpp | 49 + src/libArgParse/GrammarElement.hpp | 148 +++ src/libArgParse/GrammarInjector.hpp | 74 ++ src/libArgParse/Optional.hpp | 83 ++ src/libArgParse/ParsedElement.hpp | 180 +++ src/libArgParse/RegEx.hpp | 127 +++ src/libArgParse/Repetition.hpp | 126 ++ src/libArgParse/WhiteSpace.hpp | 88 ++ src/libCli/CMakeLists.txt | 35 + src/libCli/Call.cpp | 301 +++++ src/libCli/Call.hpp | 25 + src/libCli/Completion.cpp | 91 ++ src/libCli/Completion.hpp | 35 + src/libCli/GrammarConstruction.cpp | 455 ++++++++ src/libCli/GrammarConstruction.hpp | 26 + src/libCli/MessageParsing.cpp | 296 +++++ src/libCli/MessageParsing.hpp | 35 + src/libCli/OutputFormatting.cpp | 460 ++++++++ src/libCli/OutputFormatting.hpp | 150 +++ tests/AlternationTest.cpp | 312 +++++ tests/CMakeLists.txt | 42 + tests/ConcatenationTest.cpp | 350 ++++++ tests/FixedStringTest.cpp | 102 ++ tests/GrammarComboTests.cpp | 1013 +++++++++++++++++ tests/RepetitionTest.cpp | 185 +++ tests/testmain.cpp | 20 + third_party/CMakeLists.txt | 18 + third_party/gRPC_utils/CMakeLists.txt | 70 ++ third_party/gRPC_utils/LICENSE | 202 ++++ third_party/gRPC_utils/README | 7 + third_party/gRPC_utils/cli_call.cc | 214 ++++ third_party/gRPC_utils/cli_call.h | 98 ++ .../proto_reflection_descriptor_database.cc | 333 ++++++ .../proto_reflection_descriptor_database.h | 114 ++ third_party/gRPC_utils/reflection.proto | 136 +++ third_party/googletest | 1 + 60 files changed, 7505 insertions(+), 9 deletions(-) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 CONTRIBUTING.md create mode 100644 DCO1.1.txt create mode 100644 MAINTAINERS.md create mode 100644 PROJECT_SCOPE.md create mode 100755 build.sh create mode 100755 complete.bash create mode 100644 doc/Usage.txt create mode 100644 example.gif create mode 100644 src/CMakeLists.txt create mode 100755 src/generateVersionDefine.sh create mode 100644 src/gwhisper/CMakeLists.txt create mode 100755 src/gwhisper/generateRawStringFile.sh create mode 100644 src/gwhisper/gwhisper.cpp create mode 100644 src/libArgParse/Alternation.hpp create mode 100644 src/libArgParse/ArgParse.cpp create mode 100644 src/libArgParse/ArgParse.hpp create mode 100644 src/libArgParse/ArgParseUtils.hpp create mode 100644 src/libArgParse/CMakeLists.txt create mode 100644 src/libArgParse/Concatenation.hpp create mode 100644 src/libArgParse/FixedString.hpp create mode 100644 src/libArgParse/Grammar.hpp create mode 100644 src/libArgParse/GrammarElement.hpp create mode 100644 src/libArgParse/GrammarInjector.hpp create mode 100644 src/libArgParse/Optional.hpp create mode 100644 src/libArgParse/ParsedElement.hpp create mode 100644 src/libArgParse/RegEx.hpp create mode 100644 src/libArgParse/Repetition.hpp create mode 100644 src/libArgParse/WhiteSpace.hpp create mode 100644 src/libCli/CMakeLists.txt create mode 100644 src/libCli/Call.cpp create mode 100644 src/libCli/Call.hpp create mode 100644 src/libCli/Completion.cpp create mode 100644 src/libCli/Completion.hpp create mode 100644 src/libCli/GrammarConstruction.cpp create mode 100644 src/libCli/GrammarConstruction.hpp create mode 100644 src/libCli/MessageParsing.cpp create mode 100644 src/libCli/MessageParsing.hpp create mode 100644 src/libCli/OutputFormatting.cpp create mode 100644 src/libCli/OutputFormatting.hpp create mode 100644 tests/AlternationTest.cpp create mode 100644 tests/CMakeLists.txt create mode 100644 tests/ConcatenationTest.cpp create mode 100644 tests/FixedStringTest.cpp create mode 100644 tests/GrammarComboTests.cpp create mode 100644 tests/RepetitionTest.cpp create mode 100644 tests/testmain.cpp create mode 100644 third_party/CMakeLists.txt create mode 100644 third_party/gRPC_utils/CMakeLists.txt create mode 100644 third_party/gRPC_utils/LICENSE create mode 100644 third_party/gRPC_utils/README create mode 100644 third_party/gRPC_utils/cli_call.cc create mode 100644 third_party/gRPC_utils/cli_call.h create mode 100644 third_party/gRPC_utils/proto_reflection_descriptor_database.cc create mode 100644 third_party/gRPC_utils/proto_reflection_descriptor_database.h create mode 100644 third_party/gRPC_utils/reflection.proto create mode 160000 third_party/googletest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25a06b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +*.swp +compile_commands.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5a4e85a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "third_party/googletest"] + path = third_party/googletest + url = https://github.com/google/googletest.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..51e095c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,55 @@ +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8) +project (gWhisper) + +if(DEFINED CMAKE_BUILD_TYPE) +else() + # By default we build in Debug mode. For releases, + # please run CMAKE with + # -DCMAKE_BUILD_TYPE=RelWithDebInfo + + # non optimized with debug symbols: + set(CMAKE_BUILD_TYPE Debug) +endif() + +if (CMAKE_VERSION VERSION_LESS "3.1") + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") + else() + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + endif() +else() + set(CMAKE_CXX_STANDARD 11) +endif() + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +enable_testing() + +if(BUILD_CONFIG_USE_BOOST_REGEX) + add_definitions(-DBUILD_CONFIG_USE_BOOST_REGEX) +endif() + +# this causes all built executables to be on build directory toplevel. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR} ) + +include_directories("${PROJECT_BINARY_DIR}") +include_directories("${PROJECT_BINARY_DIR}/src") +include_directories("${PROJECT_SOURCE_DIR}") +include_directories("${PROJECT_SOURCE_DIR}/src") + +add_subdirectory(src) +add_subdirectory(tests) +add_subdirectory(third_party) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4a19045 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,151 @@ +# Contributing +## Contributing In General +Our project welcomes external contributions. If you have an itch, please feel +free to scratch it. + +To contribute code or documentation, please submit a [pull request](https://github.com/ibm/gWhisper/pulls). + +A good way to familiarize yourself with the codebase and contribution process is +to look for and tackle low-hanging fruit in the [issue tracker](https://github.com/ibm/gWhisper/issues). +Before embarking on a more ambitious contribution, please quickly [get in touch](#communication) with us and have a look at the [PROJECT_SCOPE.md](PROJECT_SCOPE.md). + +**Note: We appreciate your effort, and want to avoid a situation where a contribution +requires extensive rework (by you or by us), sits in backlog for a long time, or +cannot be accepted at all!** + +### Proposing new features + +If you would like to implement a new feature, please [raise an issue](https://github.com/ibm/gWhisper/issues) +before sending a pull request so the feature can be discussed. This is to avoid +you wasting your valuable time working on a feature that the project developers +are not interested in accepting into the code base. + +### Fixing bugs + +If you would like to fix a bug, please [raise an issue](https://github.com/ibm/gWhisper/issues) before sending a +pull request so it can be tracked. + +### Merge approval + +The project maintainers use LGTM (Looks Good To Me) in comments on the code +review to indicate acceptance. A change requires LGTMs from one of the +maintainers of each component affected. + +For a list of the maintainers, see the [MAINTAINERS.md](MAINTAINERS.md) page. + +## Communication +For feature requests, bug reports or technical discussions, please use the [issue tracker](https://github.com/ibm/gWhisper/issues). + +Depending on the need, we might create a channel on matrix.org (see [riot.im](https://about.riot.im/)) or on slack for general questions. In the meantime please contact one of the [maintainers](MAINTAINERS.md) directly for general questions or feedback. + +## Testing +As we currently do not have a CI and test framework set up, but plan to do so in the future, +we encourage contributors to ensure that existing unit-tests pass by running `make test` before +submitting a pull request. Also it is highly appreciated if you implement unit-tests +for new code. + +## Coding style guidelines +Existing code might not always follow the guidelines listed below. However please +write new code to correspond to these guidelines and feel free to re-factor old +code to be compliant. + +### Directory structure +We are trying to stay close to _Canonical Project Structure_ as described [in this proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1204r0.html). +All new code should follow the directory and naming conventions described here. + +### Indentation +This project is indented using spaces. Tabs are not allowed. One level of indentation corresponds to 4 spaces. +``` +void myMethod(bool f_condition) +{ + ... + if(f_condition) + { + if(otherCondition) + { + doSomething(); + } + } +} +``` + +### Naming conventions +- Classes: + UpperCamelCase +- Methods: + lowerCamelCase +- Variables: + lowerCamelCase + + Scope for variables is indicated by prefixes: +``` + g_ -> global scope + m_ -> object member variable + f_ -> variable given as method/function argument + f_out -> variable given as method/function argument, out parameter. + I.e. caller expects function to write/modify data referenced + to by this variable. +``` + +### Scoping brackets +The curly bracket at scope start and scope end should have the same indentation level and immediately be followed by a new-line: +``` +if(condition) +{ + something(); +} +``` + +### Source code documentation +For documenting code, please use the [doxygen style](http://www.doxygen.nl/manual/docblocks.html) method 3. +For example to document a method: +``` +/// Method for doing my stuff. +/// This method is doing my stuff by doing .... +myMethod() +... +``` + +## Legal + +Each source file must include a license header for the Apache +Software License 2.0: + +``` +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` + +We have tried to make it as easy as possible to make contributions. This +applies to how we handle the legal aspects of contribution. We use the +same approach - the [Developer's Certificate of Origin 1.1 (DCO)](DCO1.1.txt) - that the Linux® Kernel [community](https://elinux.org/Developer_Certificate_Of_Origin) +uses to manage code contributions. + +We simply ask that when submitting a patch for review, the developer +must include a sign-off statement in the commit message. + +Here is an example Signed-off-by line, which indicates that the +submitter accepts the DCO: + +``` +Signed-off-by: John Doe +``` + +You can include this automatically when you commit a change to your +local git repository using the following command: + +``` +git commit -s +``` + diff --git a/DCO1.1.txt b/DCO1.1.txt new file mode 100644 index 0000000..f440e6f --- /dev/null +++ b/DCO1.1.txt @@ -0,0 +1,25 @@ +Developer's Certificate of Origin 1.1 + + By making a contribution to this project, I certify that: + + (a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + + (b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + + (c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + + (d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..5b288cd --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,6 @@ +# MAINTAINERS + +Rainer Schoenberger - rschoe@de.ibm.com + +Eric Boehmler - erboh@de.ibm.com + diff --git a/PROJECT_SCOPE.md b/PROJECT_SCOPE.md new file mode 100644 index 0000000..97db1fa --- /dev/null +++ b/PROJECT_SCOPE.md @@ -0,0 +1,59 @@ +# Scope and design goals of the gWhisper CLI project +This document defines the scope of gWhisper. It should be helpful to +decide if a feature request or pull request will be accepted or not. + +However if you have ideas which do not quite fit the scope and you are willing to implement +them, feel free to start a discussion. + +## User personas +The scope of the project is probably defined best by looking at the intended users: + +### A gRPC service developer +- Maybe uses gRPC for the first time +- Wants to get to know gRPC by exploring a test server (hello world) +- Wants to test and debug a gRPC service implementation + +### A gRPC API user +- Maybe uses gRPC for the first time +- Has to interface a badly documented service implementation +- Wants to explore a gRPC service, as well as its exported RPCs and message types +- Wants to execute RPCs to learn how the server reacts + +### A person giving technical support +- Has deep knowledge of the service implementations +- Wants to execute RPCs to recover a corrupted server state +- Wants to execute RPCs to trigger actions which are not possible in static client implementations + +## What is important: +- Tab completion + - Big plus in usability + - No need to look at documentation +- Reflection support + - Discoverability: all provided services of the server are offered to the CLI user + - User does not have to care about proto files at all. In fact he or she does + not even need to know what a proto file is :-) +- Colors + - Modern terminals support color + - Complex data structures are much better to read with color highlighting +- Documentation + - Users should not need to use internet search engines or write mails/issues to learn how to use the tool +- The tool should be usable in a terminal + - All intended users are developers and assumed to be familiar with CLI tools + - A CLI tool tends to be more versatile and time-less than graphical interfaces + - Allows flexible use of the tool (e.g. via SSH, in scripts, etc.) + +## Nice to have, but not necessary: +- Custom output formatting + - This tends to let gRPC be used as a production tool, which is not the primary goal of gWhisper +- Syntax compatibility between releases + - This would allow use of the tool in long-living scripts or for _Machine to Machine_ communication production environments, which is not a goal of gWhisper. + +## What is not important: +- Performance for executing gRPC calls + The tool is not intended to be used for executing large numbers of RPCs or + for transfering big chunks of data. +- Output formatting in machine readable form (think JSON, XML, etc) + The tool is intended as a User Interface not for use in production environments. + If it is desired to process replies of a server with an algorithm, users + should directly implement their own gRPC client. (gRPC provides excellent + scripting interfaces) diff --git a/README.md b/README.md index 68d47bd..6be8a8e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,108 @@ -# gWhisper -gWhisper is a gRPC command-line client. -It allows to invoke gRPC calls from the command-line. - +# gWhisper - A gRPC command line tool +A gRPC command line client. +It allows to invoke gRPC Calls from the commandline and formats the replies +in a human readable format. + +![example invocation](example.gif) + The main design goals are: + - Reflection support (no proto files required) -- Tab-Completion for services, methods __and their arguments__ - (currently only supported in Bash) -- Human readable output in color +- Tab completion for + - services + - methods + - method arguments (currently only supported in Bash) + including nested types - Usable directly in the shell - -The code for this tool will be released to the public soon under the Apache v2.0 license. +- Designed with usability in mind + +Have a look at the [project scope](PROJECT_SCOPE.md) for details. + +Synopsis: + + gwhisper [OPTIONS] [:port] [=FIELD_VALUE ]... + +Execute `gwhisper --help` or click [here](doc/Usage.txt) to get detailed information and examples on how to use the tool. + +## Download gWhisper + +Clone the repository + + git clone https://github.com/IBM/gWhisper.git + +Initialize third-party submodules (currently only "google test") + + cd gWhisper + git submodule update --init + +NOTE: Please do not download gWhisper as ZIP from GitHub. GitHub currently does not support submodules, which is why you will end up with an incomplete codebase. + +## Prerequisites + +To be able to build and/or run gWhisper, you need to at least have the following dependencies installed on your system: + +- cmake +- A C++ compiler +- gRPC [link](https://github.com/grpc/grpc) + including the protoc plugin, which is packaged separately in some linux distributions +- protocolBuffers [link](https://github.com/protocolbuffers/protobuf) + +On Fedora you can install the prerequisites with: + + yum install cmake gcc-c++ protoc grpc grpc-devel grpc-plugins + +On other distributions we tried, gRPC and/or protobuf packages seem to be not available, outdated or incomplete (missing gRPC protoc plugin). +In this case, please build and install gRPC and protocolBuffers from the official sources. + +## Build and run + +Build the code + + ./build.sh + +Source the bash completion file (for tab completion) + + . ./complete.bash + +Run the executable (use TabCompletion): + + ./build/gwhisper [] + +NOTE: +If you are not building in a checked out git repository you should set the environment variable `GWHISPER_BUILD_VERSION` to the appropriate version of the source code. +This will end up as part of the version string, returned when calling `gWhisper --version`. +In case you are building within the git repository, the version is automatically determined during the build. You may however add additional information to the version string by using this environment variable. + +## Current development status + +This is in a pre-release state. Basic functionality is implemented, but you may experience bugs. +Feel free to try it out and provide feedback/contributions. + +What is working: + +- Tab Completion (bash only) +- Calling RPCs (unary + server-streaming) +- Output of all types supported by protocol buffers + +Some notable things which are not yet working: + +- Input: OneOf fields +- Input: Client streaming RPCs +- Input: Escaping of control characters (":@.(,") +- Completion: Support for shells other than BASH (e.g. zsh, fish) +- Security: Authentication / Encryption of channels +- Performance: Caching of reflection queries + +## Supported platforms + +All development and testing is done on ubuntu linux (mostly 16.04). We expect no bigger problems +with building and running this software on different linux distributions. + +## Reporting issues + +Please use the GitGub [issues tab](https://github.com/ibm/gWhisper/issues). +Be sure to search issues first to avoid duplicate entries. + +## Contribute + +Please have a look at [CONTRIBUTE.md](CONTRIBUTING.md). diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..eb8b2be --- /dev/null +++ b/build.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +SOURCE_DIR="$PWD" +BUILD_DIR="$SOURCE_DIR/build" +if [ ! -f "$BUILD_DIR/Makefile" ]; then + mkdir $BUILD_DIR + cd $BUILD_DIR + cmake $SOURCE_DIR "$@" + cd $SOURCE_DIR +fi +cd $BUILD_DIR +make -j8 + diff --git a/complete.bash b/complete.bash new file mode 100755 index 0000000..09d144e --- /dev/null +++ b/complete.bash @@ -0,0 +1,70 @@ +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a very simple completion function. It essentially offloads all +# completion work to the gWhisper tool itself, by issuing a completion request. +# The output of gWhisper is then used to fill the COMPREPLY variable. +_gWhisperCompletion() +{ + # we interpret the first word as the gWhisper command itself: + COMMANDNAME=${COMP_WORDS[0]} + if [ $GWHISPER_DEBUG_COMPLETION ] + then + echo "## completion for command: ######################################" + echo "$COMMANDNAME" + fi + + # index of the first character of command arguments + ARG_START=$(( ${#COMMANDNAME} + 1 )) + # length of characters in arguments until (excluding) cursor position + ARG_LEN=$(( $COMP_POINT - $ARG_START )) + + ARGS=${COMP_LINE:$ARG_START:$ARG_LEN} + if [ $GWHISPER_DEBUG_COMPLETION ] + then + echo "## arguments to complete: #######################################" + echo "\"$ARGS\"" + fi + + # we retrieve completion choices by just executing gWhisper with the + # --complete argument in the beginning: + SUGGESTIONS=$($COMMANDNAME "--complete $ARGS") + + if [ $GWHISPER_DEBUG_COMPLETION ] + then + echo "## gWhisper returned the following completion suggestion: ##########" + echo "\"$SUGGESTIONS\"" + fi + + # We parse the suggestions returned by gWhisper into an array. We only split + # on newline, wo we have to change $IFS and restore after the conversion: + OIFS=$IFS; + IFS=$'\n'; # we only split on newlines + COMPREPLY=($SUGGESTIONS) + IFS=$OIFS; + + + if [ $GWHISPER_DEBUG_COMPLETION ] + then + echo "## parsed suggestion string into array: #########################" + for i in "${COMPREPLY[@]}" + do + echo "entry: '$i'" + done + fi + + return 0 +} + +complete -o nospace -F _gWhisperCompletion gwhisper diff --git a/doc/Usage.txt b/doc/Usage.txt new file mode 100644 index 0000000..209c68b --- /dev/null +++ b/doc/Usage.txt @@ -0,0 +1,88 @@ +gWhisper - A gRPC command-line client +This command allows to call gRPC RPCs on any gRPC server with reflection enabled. +You may use TAB completion for all arguments (currently only supported in BASH). + +SYNOPSIS: +gwhisper [OPTIONS] [:port] [=FIELD_VALUE ]... + +The default TCP port used to connect to a gRPC server is 50051. + +OPTIONS: + -h + --help + Shows this help. + --noColor + Disables colors in the output of gRPC replies. + --customOutput OUTPUT_FORMAT + Instead of printing the reply message using the default human readable + format, a custom format as specified in OUTPUT_FORMAT is used. + See OUTPUT_FORMAT section for a description of the OUTPUT_FORMAT language. + Note that this is an experimental feature and will be documented in detail, + once finished. + --complete + Shows possible next arguments. + The output is rendered to be usable as input for bash-completion. + --dot + Prints a graphviz digraph, representing the current grammar of the parser. + +FIELD_VALUE: + Field values in the request message may be specified as follows + - integers: + decimal (e.g. 46, -46, +46) + hexadecimal (e.g. 0x45ab, -0x45ab, +0x45ab) + octal (e.g. 078, -078, +078) + - floats: + decimal (e.g. 31, -67.421e-8, 83.456, 23.3e8) + hexadecimal (e.g. -0xa7b6p-7) + - bytes: + As a hex number (e.g. 0xab4b2f5e9d7f) + NOTE: Only multiple of 8 bits possible + - strings: + As a string without quotes. (e.g. ThisIsAString) + NOTE: currently strings may not contain ' ', ',' or ':' + - enum values: + As the enum value name + Enum values are tab-completed + - bools + true, false, 1, 0 + Bool values are tab-completed + - nested types: + The nested type fields enclosed in colons. E.g. target=:field1=1 field2=2 : + Nested types are tab-completed + NOTE: currently after each field value a space is required + - repeated fields: + A list of field values enclosed in colons, separated with a comma and a whitespace. + E.g. values=:2, 4, 16, 32: + Repeated fields are tab-completed + - map fields: + As a repeated field with nested key, value pairs (protobuf representation of maps). + E.g. my_map=::key=5 value=testval :, :key=40 value=anotherValue:: + Map fields are tab-completed + +OUTPUT_FORMAT: + This is a program written in a very simple formatting language. A valid program has + the following form: + @ (.)+ : ( || // ) : + Example: + ./gwhisper --customOutput @.listOfDevices:Found device /device_name/$'\n': 127.0.0.1 discoveryService.devices GetDevices + + Output: + Found device Mouse + Found device Keyboard + Found device Screen + ... + This will search for any number of fields with a name "listOfDevices" in the RPC reply message. For each match (multiple in case of repeated field), The string `"Found device /device_name/\n"` is printed with `/device_name/` replaced by the value of the field `"device_name"`. + + +EXAMPLES: + gWhisper exampledomain.org:50059 bakery orderCookies amount=5 + Connects to the gRPC server running at exampledomain.org on TCP port 50059 + and calls the orderCookies RPC method of the bakery service. + A request message will be passed to the RPC call with the 'amount' field + set to 5. Other fields, which might be present, are left at default values. + + gWhisper [2001:db8::2:1]:50059 bakery orderCookies config=:chocolate=true smarties=false : amount=5 + Uses IPv6 address and nested types in the request message. + + gWhisper 192.168.0.43 bakery orderCookies amount=0xa + Uses IPv4 address, default TCP port of 50051 and field value as hex. diff --git a/example.gif b/example.gif new file mode 100644 index 0000000000000000000000000000000000000000..8f8c14cccc9bf7e96b8ca2c43ada98076477c68a GIT binary patch literal 362359 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

;?MUa}?8hHZHZcSS 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

k)UU)7 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 literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..11674eb --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8) + +add_subdirectory(libArgParse) +add_subdirectory(libCli) +add_subdirectory(gwhisper) + +add_custom_target(compileCommands + COMMAND cp ${CMAKE_BINARY_DIR}/compile_commands.json ${CMAKE_SOURCE_DIR}/compile_commands.json + ) + +add_custom_target(versionDefine + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/generateVersionDefine.sh > ${CMAKE_CURRENT_BINARY_DIR}/versionDefine.h + ) diff --git a/src/generateVersionDefine.sh b/src/generateVersionDefine.sh new file mode 100755 index 0000000..0e09407 --- /dev/null +++ b/src/generateVersionDefine.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# get build date +DATE=`date +%Y-%m-%d_%H:%M:%S` + + +if [ -z "`command -v git`" ]; then + echo "$GWHISPER_BUILD_VERSION Build date: $DATE Build pwd: $PWD" + exit 0 +fi + +git status &> /dev/null +if [ $? -ne 0 ]; then + echo "$GWHISPER_BUILD_VERSION Build date: $DATE Build pwd: $PWD" + exit 0 +fi + +# get unique readable commit identification +gitId=$(git describe --tags) + +# check for uncommited changes +uncommitedChanges="" +git diff-index --quiet HEAD -- +if [ $? -eq 0 ]; then + uncommitedChanges="NoUncommitedChanges" +else + uncommitedChanges="UncommitedChanges" +fi + +# check for untracked files present +untrackedFiles="" +tmpUntracked=$(git ls-files --other --directory --exclude-standard) +if [ -z "$tmpUntracked" ]; then + untrackedFiles="NoUntrackedFiles" +else + untrackedFiles="UntrackedFiles" +fi + +versionString="$GWHISPER_BUILD_VERSION Git identifier: ${gitId} Build date: ${DATE} ${uncommitedChanges} ${untrackedFiles}" + +echo "#pragma once" +echo "#define GWHISPER_BUILD_VERSION \"$versionString\"" diff --git a/src/gwhisper/CMakeLists.txt b/src/gwhisper/CMakeLists.txt new file mode 100644 index 0000000..87d306e --- /dev/null +++ b/src/gwhisper/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8) + +set(TARGET_NAME "gwhisper") + +# this converts the help string into a raw string for use with --help +add_custom_command( + OUTPUT HelpString.h + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/generateRawStringFile.sh ${CMAKE_SOURCE_DIR}/doc/Usage.txt ${CMAKE_CURRENT_BINARY_DIR}/HelpString.h + DEPENDS ${CMAKE_SOURCE_DIR}/doc/Usage.txt + ) + +set(TARGET_SRC + gwhisper.cpp + HelpString.h + ) +add_executable(${TARGET_NAME} ${TARGET_SRC}) +target_link_libraries ( ${TARGET_NAME} + cli + ) + +add_dependencies(${TARGET_NAME} compileCommands versionDefine) diff --git a/src/gwhisper/generateRawStringFile.sh b/src/gwhisper/generateRawStringFile.sh new file mode 100755 index 0000000..001ae6e --- /dev/null +++ b/src/gwhisper/generateRawStringFile.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +if [ "$#" -lt 2 ]; then + echo "Not enough arguments. Expected source and destination file" + exit 1 +fi +echo -n "R\"(" > $2 +cat $1 >>$2 +echo ")\"" >> $2 diff --git a/src/gwhisper/gwhisper.cpp b/src/gwhisper/gwhisper.cpp new file mode 100644 index 0000000..54e2f6c --- /dev/null +++ b/src/gwhisper/gwhisper.cpp @@ -0,0 +1,109 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include +#include // generated during build + +using namespace ArgParse; + +std::string getArgsAsString(int argc, char **argv) +{ + std::string result; + bool first = true; + for(int i = 1; i +; + +int main(int argc, char **argv) +{ + // First we construct the initial Grammar for the CLI tool: + Grammar grammarPool; + GrammarElement * grammarRoot = cli::constructGrammar(grammarPool); + std::string baseGrammar = grammarRoot->toString(); + + // Now we parse the given arguments using the grammar: + std::string args = getArgsAsString(argc, argv); + ParsedElement parseTree; + ParseRc rc = grammarRoot->parse(args.c_str(), parseTree); + + // TODO: add option to print parse tree after parsing: + // // Now we act according to the parse tree: + //std::cout << parseTree.getDebugString() << "\n"; + + if(parseTree.findFirstChild("DotExport") != "") + { + std::cout << grammarPool.getDotGraph(); + return 0; + } + + if(parseTree.findFirstChild("Complete") != "") + { + bool completeDebug = (parseTree.findFirstChild("CompleteDebug") != ""); + cli::printBashCompletions(rc.candidates, parseTree, args, completeDebug); + return 0; + } + + if(parseTree.findFirstChild("Version") != "") + { + std::cout << GWHISPER_BUILD_VERSION << std::endl; + return 0; + } + + if(parseTree.findFirstChild("Help") != "") + { + printf("%s", g_helpString); + return 0; + } + + if(rc.isGood() && (rc.lenParsedSuccessfully == args.length())) + { + return cli::call(parseTree); + } + + std::cout << "Parse failed. "; + std::cout << "Parsed until: '" << parseTree.getMatchedString() << "'" << std::endl; + //std::cout << parseTree.getDebugString() << "\n"; + if(rc. candidates.size() > 0) + { + std::cout << "Possible Candidates:"; + for(auto candidate : rc.candidates) + { + //printf("\nchoice:\n%s", candidate->getDebugString().c_str()); + printf("\n '%s'", candidate->getMatchedString().c_str()); + } + std::cout << std::endl; + } + + return -1; +} diff --git a/src/libArgParse/Alternation.hpp b/src/libArgParse/Alternation.hpp new file mode 100644 index 0000000..05e3af3 --- /dev/null +++ b/src/libArgParse/Alternation.hpp @@ -0,0 +1,158 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include + +namespace ArgParse +{ +class Alternation : public GrammarElement +{ + public: + Alternation(const std::string & f_elementName = ""): + GrammarElement("Alternation", f_elementName) + { + } + + virtual std::string toString() override + { + std::string result; + if(m_children.size()>1) + { + result += "("; + } + bool first = true; + for(auto child: m_children) + { + if(!first) + { + result += "||"; + } + else + { + first = false; + } + result += child->toString(); + } + if(m_children.size()>1) + { + result += ")"; + } + return result; + } + + virtual ParseRc parse(const char * f_string, ParsedElement & f_out_ParsedElement, size_t candidateDepth = 1, size_t startChild = 0) override + { + ParseRc rc; + f_out_ParsedElement.setGrammarElement(this); + + //std::cout << "Alternation " << std::to_string(m_instanceId) << ": parsing '" << f_string << "'\n"; + + std::vector > candidateList; + + std::shared_ptr winner; + std::shared_ptr maybeWinner; + GrammarElement * maybeWinnerGE; + size_t maybeCount = 0; + size_t newCandidateDepth = candidateDepth; + if(candidateDepth > 0) + { + // New idea: never allow forks by default. + // if we realize in the end, that we are unique, we parse + // again, this time allowing forks + newCandidateDepth--; + } + for(auto child : m_children) + { + auto newParsedElement = std::make_shared(&f_out_ParsedElement); + ParseRc childRc = child->parse(f_string, *newParsedElement, newCandidateDepth); + //std::cout << " Alternation pass1 "<< std::to_string(m_instanceId) << " parsed child ? rc=" << childRc.toString() << " #candidates: " << std::to_string(childRc.candidates.size()) << std::endl; + if(childRc.isGood()) + { + if(rc.lenParsedSuccessfully <= childRc.lenParsedSuccessfully) + { + // FIXME: what about optional childs? They always succeed but have candidates + // -> this is low prio, as optional childs in alternations do not really make sense, but should be supported in the end anyways + rc.lenParsedSuccessfully = childRc.lenParsedSuccessfully; + rc.lenParsed = childRc.lenParsed; + winner = newParsedElement; + } + } + if((not childRc.isGood()) && (childRc.errorType != ParseRc::ErrorType::unexpectedText)) + { + maybeWinner = newParsedElement; + maybeWinnerGE = child; + maybeCount++; + + // this is a candidate for completion + //std::cout << " Alt"<< std::to_string(m_instanceId) << "one child rc=" << childRc.toString() << std::endl; + for(auto candidate : childRc.candidates) + { + //std::cout << " Alternation"<< std::to_string(m_instanceId) << ": have possible candidate: '" << candidate->getMatchedString() << "'" << std::endl; + candidateList.push_back(candidate); + } + } + } + + if(winner != nullptr) + { + f_out_ParsedElement.addChild(winner); + } + else + { + if(maybeCount == 1) + { + // we have had enough text to uniquely select one of the alternates + // => we can add it to the f_out_ParsedElement: + f_out_ParsedElement.addChild(maybeWinner); + // in this case we could uniquely identify a candidate :) + // so we need to parse again for candidates, this time allowing for forks + candidateList.clear(); + ParsedElement unused; + ParseRc childRc = maybeWinnerGE->parse(f_string, unused, candidateDepth); + candidateList = childRc.candidates; + //std::cout << " Alternation pass2 "<< std::to_string(m_instanceId) << " parsed child ? rc=" << childRc.toString() << " #candidates: " << std::to_string(childRc.candidates.size()) << std::endl; + } + + // merge RCs + if(m_children.size() == 0) + { + rc.errorType = ParseRc::ErrorType::success; + } + else if(candidateList.size() == 0) + { + rc.errorType = ParseRc::ErrorType::unexpectedText; + } + else + { + rc.errorType = ParseRc::ErrorType::missingText; + rc.lenParsed = strlen(f_string); + } + } + + // add all candidates to the candidate list + for(auto candidate : candidateList) + { + //std::cout << "Alt " << std::to_string(m_instanceId) << " handling candidate '" << candidate->getMatchedString() << "'" << std::endl; + auto candidateRoot = std::make_shared(f_out_ParsedElement.getParent()); + candidateRoot->setGrammarElement(this); + candidateRoot->addChild(candidate); + rc.candidates.push_back(candidateRoot); + } + + return rc; + } +}; + +} diff --git a/src/libArgParse/ArgParse.cpp b/src/libArgParse/ArgParse.cpp new file mode 100644 index 0000000..ddcae00 --- /dev/null +++ b/src/libArgParse/ArgParse.cpp @@ -0,0 +1,106 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +//uint32_t ArgParse::GrammarElement::m_instanceCounter = 0; + +std::string ArgParse::ParsedElement::getDebugString(std::string f_prefix) +{ + std::string result; + if(m_grammarElement == nullptr) + { + return "!!Uninitialized Element!!"; + } + result += f_prefix + m_grammarElement->getTypeName() + "(" + m_grammarElement->getElementName() + "): \"" + getMatchedString() + "\" (" + std::string(m_stops ? "stopped" : "alive") + ")\n"; + for(auto child : m_children) + { + result += child->getDebugString(f_prefix + " "); + } + return result; +} + +std::string ArgParse::ParsedElement::findFirstChild(const std::string & f_elementName) +{ + bool found = false; + ParsedElement & result = findFirstSubTree(f_elementName, found); + if(found) + { + return result.getMatchedString(); + } + else + { + return ""; + } +} + +void ArgParse::ParsedElement::findAllSubTrees(const std::string & f_elementName, std::vector & f_out_result, bool f_doNotSearchChildsOfMatchingElements) +{ + bool match = false; + if(m_grammarElement->getElementName() == f_elementName) + { + match = true; + f_out_result.push_back(this); + if(f_doNotSearchChildsOfMatchingElements) + { + return; + } + } + + for(auto child : m_children) + { + child->findAllSubTrees(f_elementName, f_out_result); + } +} + +ArgParse::ParsedElement & ArgParse::ParsedElement::findFirstSubTree(const std::string & f_elementName, bool & f_out_found) +{ + //std::cout << "searching for " << f_elementName << " going through " << m_grammarElement->getTypeName() << " " << m_grammarElement->getElementName() << std::endl; + if(m_grammarElement->getElementName() == f_elementName) + { + f_out_found = true; + //std::cout << "searched for " << f_elementName << " returning true\n"; + return *this; + } + else + { + for(auto child : m_children) + { + bool found = false; + ParsedElement & result = child->findFirstSubTree(f_elementName, found); + if(found) + { + f_out_found = true; + return result; + } + } + } + + f_out_found = false; + return *this; +} + +std::string ArgParse::Grammar::getDotGraph() +{ + //std::cout << "GENERATING DOT GRAPH\n"; + std::string result = "digraph {\n"; + result += "ordering=out;\n"; + for(auto & node : m_nodes) + { + //printf("getting info for node %p\n", node.get()); + result += node->getDotNode(); + } + result += "}\n"; + return result; +} diff --git a/src/libArgParse/ArgParse.hpp b/src/libArgParse/ArgParse.hpp new file mode 100644 index 0000000..d974888 --- /dev/null +++ b/src/libArgParse/ArgParse.hpp @@ -0,0 +1,57 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/// This is a summary header file for a group of classes which compose an argument parsing system. +/// The high-level concept of this framework is as follows: +/// - GrammarElement instances are created from a memory pool and compose a graph representing the grammar to parse. +/// - Multiple derivations of GrammarElement implement different gramar features (Alternation, Concatenation, FixedString, Optional, RegEx, ...) +/// Those elements provide the building blocks for the grammar to be implemented by the user. +/// - GrammarElements may be combined by calling the addChild() methods. +/// - GrammarElements may be associated with a string tag, which will be assigned to all elements whcih get parsed by this element. (similar to backreferences in regex) +/// - Once the Grammar is constructed, the user may call parse() with a given string on one of the GramarElements (typically the root/start element) +/// - The parse() function will generate +/// - a parse tree containing all parsed values, composed of ParsedElement instances +/// - a list of possible parse trees results if parse was incomplete. Those may be used for completion. +/// - additional meta information about the parse (return code, parsed length, etc.) +/// - The parse tree provides utility functions to access parsed data: +/// - search of tagged elements +/// - interation/inspection of the tree +/// - Each ParsedElement contains a reference to its associated GrammarElement. + +// TODO: individual classes and their member functions of this framework are not yet documented well enough + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + diff --git a/src/libArgParse/ArgParseUtils.hpp b/src/libArgParse/ArgParseUtils.hpp new file mode 100644 index 0000000..65369e6 --- /dev/null +++ b/src/libArgParse/ArgParseUtils.hpp @@ -0,0 +1,67 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include +#include +#include + +namespace ArgParse +{ +struct ParseRc +{ + enum class ErrorType + { + success, + missingText, + unexpectedText, + }; + + ErrorType errorType = ErrorType::success; + size_t lenParsedSuccessfully = 0; + size_t lenParsed = 0; + + std::string toString() + { + switch(errorType) + { + case ErrorType::success: + return "success"; + break; + case ErrorType::missingText: + return "missingText"; + break; + case ErrorType::unexpectedText: + return "unexpectedText"; + break; + default: + return "???"; + break; + } + } + + bool isGood() + { + return errorType == ErrorType::success; + } + + bool isBad() + { + return not isGood(); + } + + std::vector > candidates; +}; +} diff --git a/src/libArgParse/CMakeLists.txt b/src/libArgParse/CMakeLists.txt new file mode 100644 index 0000000..52a2ccf --- /dev/null +++ b/src/libArgParse/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8) + +set (CMAKE_CXX_STANDARD 11) + +set(TARGET_NAME "ArgParse") +set(TARGET_SRC + ArgParse.cpp + ) +add_library(${TARGET_NAME} ${TARGET_SRC}) + +target_link_libraries(${TARGET_NAME} + #boost_regex + ) diff --git a/src/libArgParse/Concatenation.hpp b/src/libArgParse/Concatenation.hpp new file mode 100644 index 0000000..3b11ed0 --- /dev/null +++ b/src/libArgParse/Concatenation.hpp @@ -0,0 +1,164 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +namespace ArgParse +{ +class Concatenation : public GrammarElement +{ + public: + Concatenation(const std::string & f_elementName = "") : + GrammarElement("Concatenation", f_elementName) + { + } + + virtual std::string toString() override + { + std::string result; + if(m_children.size()>1) + { + result += "("; + } + //bool first = true; + for(auto child: m_children) + { + //if(!first) + //{ + // result += " "; + //} + //else + //{ + // first = false; + //} + result += child->toString(); + } + if(m_children.size()>1) + { + result += ")"; + } + return result; + } + + virtual ParseRc parse(const char * f_string, ParsedElement & f_out_ParsedElement, size_t candidateDepth = 1, size_t startChild = 0) override + { + //std::cout << "Concat " << std::to_string(m_instanceId) << " parsing '" << std::string(f_string) << "' cd=" << std::to_string(candidateDepth) << std::endl; + ParseRc rc; + ParseRc childRc; + f_out_ParsedElement.setGrammarElement(this); + + + //std::cout << "Concat "<< std::to_string(m_instanceId) << " parsing '" << std::string(f_string) << "' starting with child " << std::to_string(startChild) << "/" << std::to_string(m_children.size()-1)<< "' cd=" << std::to_string(candidateDepth) << std::endl; + for(size_t i = startChild; (i(&f_out_ParsedElement); + childRc = child->parse(&f_string[rc.lenParsed], *newParsedElement); + //std::cout << " Concat "<< std::to_string(m_instanceId) << " parsed child" << std::to_string(i) << " rc=" << childRc.toString() << " #candidates: " << std::to_string(childRc.candidates.size()) << std::endl; + rc.lenParsed += childRc.lenParsed; + rc.lenParsedSuccessfully += childRc.lenParsedSuccessfully; + + // merge RCs + rc.errorType = childRc.errorType; + + if(childRc.candidates.size() > 0) + { + for(auto candidate : childRc.candidates) + { + //std::cout << "Concat " << std::to_string(m_instanceId) << " handling candidate from child " << std::to_string(i)<< " '" << candidate->getMatchedString() << "' cd=" << std::to_string(candidateDepth) << std::endl; + // we create a new candidate (same tree level as f_out_ParsedElement) + auto candidateRoot = std::make_shared(f_out_ParsedElement.getParent()); + candidateRoot->setGrammarElement(this); + + // first add all previous childs to the new root (from concatenation before the failing element): + for(auto oldChild : f_out_ParsedElement.getChildren()) + { + candidateRoot->getChildren().push_back(oldChild); + } + + // add the candidate to the new root: + candidateRoot->addChild(candidate); + + // decide on future recursion depth: + size_t newCandidateDepth = candidateDepth; + if(childRc.candidates.size() > 1) + { + // if we have multiple candidates, we reduce the candidateDepth by 1 + newCandidateDepth--; + } + + // we only go on parsing the next childs, if we have only one candidate (unique follow up possible) + // or we still are allowed to fork + // Otherwise we just do not return ANY candidates + if(candidateDepth > 0 || childRc.candidates.size() == 1) + { + // Now continue parsing the other childs, using this candidate as a base: + ParseRc candidateRc; + if(!candidateRoot->isStopped()) + { + //std::cout << " -> forking for this candidate" << std::endl; + candidateRc = parse("", *candidateRoot, newCandidateDepth, i+1); + } + if(candidateRc.candidates.size() == 0) + { + // if we could not find any more candidates, we add this candidate: + candidateRoot->setStops(); //If we could not find a candidate we need to stop compeleting TODO: write unit test to ensure this + rc.candidates.push_back(candidateRoot); + //std::cout << "Concat " << std::to_string(m_instanceId) << " 0push candidate '" << candidateRoot->getMatchedString() << "'" << std::endl; + } + else if(candidateRc.candidates.size() == 1) + { + rc.candidates.push_back(candidateRc.candidates[0]); + //std::cout << "Concat " << std::to_string(m_instanceId) << " 1push candidate '" << candidateRc.candidates[0]->getMatchedString() << "'" << std::endl; + } + else + { + // if we could find more candidates in the recursion, we add them instead :) + // but only if we are still allowed to + for(auto cnd : candidateRc.candidates) + { + rc.candidates.push_back(cnd); + //std::cout << "Concat " << std::to_string(m_instanceId) << " Mpush candidate '" << candidateRc.candidates[0]->getMatchedString() << "'" << std::endl; + } + } + } + } + + } + + if(childRc.isGood() || childRc.errorType == ParseRc::ErrorType::missingText) + { + //std::cout << "Concat "<< std::to_string(m_instanceId) << " have good child " << std::to_string(i) << " matched string: " << newParsedElement->getMatchedString() << std::endl; + // need to do this AFTER candidate avaluation, as some parse + // methods will return good rc but still have candidates (e.g. repetition) + // in this case we whould add their results to their candidates again + f_out_ParsedElement.addChild(newParsedElement); + if(not childRc.isGood()) + { + // TODO: add this for other GrammarElements too + // TODO: write unit test for this! + f_out_ParsedElement.setIncompleteParse(); + } + } + } + + //std::cout << "Concat "<< std::to_string(m_instanceId) << " returning rc=" << rc.toString() << std::endl; + + return rc; + } +}; + +} diff --git a/src/libArgParse/FixedString.hpp b/src/libArgParse/FixedString.hpp new file mode 100644 index 0000000..02aa712 --- /dev/null +++ b/src/libArgParse/FixedString.hpp @@ -0,0 +1,86 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include + +namespace ArgParse +{ +class FixedString : public GrammarElement +{ + public: + FixedString(const std::string & f_string, const std::string & f_elementName = "") : + GrammarElement("FixedString", f_elementName), + m_string(f_string) + { + } + + virtual std::string toString() override + { + return m_string; + } + + virtual ParseRc parse(const char * f_string, ParsedElement & f_out_ParsedElement, size_t candidateDepth = 1, size_t startChild = 0) override + { + ParseRc rc; + ParseRc childRc; + f_out_ParsedElement.setGrammarElement(this); + + //std::cout << " FixedString"<< std::to_string(m_instanceId) << "parsing '" << std::string(f_string) << "'" << std::endl; + //printf("comparing: '%s' == '%s'\n", f_string, m_string.c_str()); + if(strncmp(m_string.c_str(), f_string, m_string.size()) == 0) + { + //printf(" -> same\n"); + rc.errorType = ParseRc::ErrorType::success; + rc.lenParsedSuccessfully = m_string.size(); + rc.lenParsed = m_string.size(); + f_out_ParsedElement.setMatchedString(m_string); + } + else + { + rc.lenParsedSuccessfully = 0; + if(strncmp(m_string.c_str(), f_string, strlen(f_string)) == 0) + { + rc.lenParsed = strlen(f_string); + // have a candidate for completion :) + //printf(" -> completion possible\n"); + // create a candidate: + auto candidate = std::make_shared(&f_out_ParsedElement); + candidate->setGrammarElement(this); + candidate->setMatchedString(m_string); + rc.candidates.push_back(candidate); + + // set rc + rc.errorType = ParseRc::ErrorType::missingText; + } + else + { + //printf(" -> totally different\n"); + rc.errorType = ParseRc::ErrorType::unexpectedText; + } + } + //std::cout << " FixedString"<< std::to_string(m_instanceId) << " rc=" << rc.toString() << std::endl; + return rc; + } + virtual std::string getDotNode() override + { + std::string result = ""; + result += "n" + std::to_string(m_instanceId) + "[label=\"" + std::to_string(m_instanceId) + " " + m_typeName + " " + m_elementName + " " + m_tag + "'" + m_string + "'\"];\n"; + return result; + } + private: + const std::string m_string; +}; + +} diff --git a/src/libArgParse/Grammar.hpp b/src/libArgParse/Grammar.hpp new file mode 100644 index 0000000..bfd8304 --- /dev/null +++ b/src/libArgParse/Grammar.hpp @@ -0,0 +1,49 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +namespace ArgParse +{ + + class Grammar + { + public: + template + T * createElement( Args&&... f_args ) + { + T * newNodePtr = new T(std::forward(f_args)...); + std::unique_ptr newNode(newNodePtr); + m_nodes.push_back(std::move(newNode)); + return newNodePtr; + } + + void setRoot( GrammarElement * f_rootElement) + { + m_rootElement = f_rootElement; + } + + std::string getDotGraph(); + + virtual ~Grammar() + { + } + + private: + std::vector< std::unique_ptr > m_nodes; + GrammarElement * m_rootElement; + }; + +} + diff --git a/src/libArgParse/GrammarElement.hpp b/src/libArgParse/GrammarElement.hpp new file mode 100644 index 0000000..704284e --- /dev/null +++ b/src/libArgParse/GrammarElement.hpp @@ -0,0 +1,148 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include +#include +#include +#include + +namespace ArgParse +{ +// a Graph +class GrammarElement; + +class GrammarElement +{ + public: + GrammarElement(std::string f_typeName, std::string f_elementName = "") : + m_typeName(f_typeName), + m_elementName(f_elementName), + m_instanceId(getAndIncrementInstanceCounter()) + { + } + + /* Parses a given string into a parse tree. + * @param f_string string to be parsed (null terminated cstring) + * @param f_out_ParsedElement Reference to a ParsedElement, which will + * be filled with the parse result (parse tree). + * i.e. this Grammar element will be parsed into the given ParsedElement f_out_ParsedElement. + * The f_out_ParsedElement will always reference this GrammarElement + * instance after it is passed to this method. + * If the parse method could uniquely identify or parse a child, the + * f_out_ParsedElement object will also have a child referencing to the + * child-GrammarElement. + * @param candidateDepth This parameter controls the maximum recursion/fork + * depth when analyzing candidates. + * This means, that when a Element could continue parsing with X1 > 1 candidates, + * Parsing is tried for each of these candidates, but candidateDepth is reduced by 1. + * If those candidates return X2 sub-candidates the following will happen: + * If X2 > 1 and the candidateDepth is zero, parsing will stop and the + * returned candidates are ignored. + * If X2 > 1 and the candidateDepth is > 0, parsing is done for all + * those childs with a candidateDepth reduced by 1 again. + * If X2 == 1 parsing will continue + * NOTE: This description describes the behavior of Concatenations as an example + * Behavior for other Elements is similar, but might differ slightly. + * For example Alternations always continues parsing + * (think: An Alternation with an alternation as a child is + * semantically the same as a single Alternation with merged childs) + * Currently only a candidateDepth of 1 is tested. + * @param startChild The child number to start parsing from. Can be + * used to start parsing from a specific gramar element. + * Currently this is mostly used internally. + * TODO: Think about making this protected + * @returns ParseRc with the following attributes: + * candidate list containing a list of ParsedElements which + * could be used in place of f_out_ParsedElement. + */ + virtual ParseRc parse(const char * f_string, ParsedElement & f_out_ParsedElement, size_t candidateDepth = 1, size_t startChild = 0) = 0; + + virtual std::string toString() + { + std::string result; + result = m_typeName + "(" + m_elementName + ":" + std::to_string(m_instanceId) + ")" + "{"; + for(auto child: m_children) + { + result += child->toString(); + result += ", "; + } + result += "}"; + return result; + } + + GrammarElement * addChild(GrammarElement * f_child) + { + f_child->setParent(this); + m_children.push_back(f_child); + return this; + } + + void setParent(GrammarElement * f_parent) + { + m_parent = f_parent; + } + + // TODO: what is the difference between tag and ElementName?? + // tag does not seem to be used + std::string getTag() const + { + return m_tag; + } + + std::string getTypeName() const + { + return m_typeName; + } + + std::string getElementName() const + { + return m_elementName; + } + + virtual ~GrammarElement() + { + + } + + virtual std::string getDotNode() + { + std::string result = ""; + result += "n" + std::to_string(m_instanceId) + "[label=\"" + std::to_string(m_instanceId) + " " + m_typeName + " " + m_elementName + " " + m_tag + "\"];\n"; + for(auto child : m_children) + { + result += " n" + std::to_string(m_instanceId) + " -> n" + std::to_string(child->m_instanceId) + ";\n"; + } + //std::cout << " got dot string: " << result; + return result; + } + protected: + GrammarElement * m_parent; + std::vector< GrammarElement * > m_children; + + std::string m_tag; + const std::string m_typeName; + const std::string m_elementName; + const uint32_t m_instanceId; + private: + static uint32_t getAndIncrementInstanceCounter() + { + static uint32_t instanceCounter = 0; + return instanceCounter++; + } +}; + + +} diff --git a/src/libArgParse/GrammarInjector.hpp b/src/libArgParse/GrammarInjector.hpp new file mode 100644 index 0000000..9605684 --- /dev/null +++ b/src/libArgParse/GrammarInjector.hpp @@ -0,0 +1,74 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include + +namespace ArgParse +{ + +class GrammarInjector : public GrammarElement +{ + public: + GrammarInjector(std::string f_typeName, const std::string & f_elementName = "") : + GrammarElement("GrammarInjector::" + f_typeName, f_elementName) + { + } + virtual ParseRc parse(const char * f_string, ParsedElement & f_out_ParsedElement, size_t candidateDepth = 1, size_t startChild = 0) override final + { + if(m_children.size() == 0) + { + // we first need to inject new grammar: + addChild(getGrammar(f_out_ParsedElement.getRoot())); + } + + f_out_ParsedElement.setGrammarElement(this); + auto child = std::make_shared(&f_out_ParsedElement); + // we transparently skip to parsing the new child + //return m_children[0]->parse(f_string, f_out_ParsedElement, candidateDepth); + ParseRc childRc = m_children[0]->parse(f_string, *child, candidateDepth); + + f_out_ParsedElement.addChild(child); + + return childRc; + } + + virtual GrammarElement * getGrammar(ParsedElement * f_parseTree) = 0; +}; + + +class GrammarInjectorTest : public GrammarInjector +{ + public: + GrammarInjectorTest(Grammar & f_grammar) : + GrammarInjector("Test"), + m_grammar(f_grammar) + { + } + + virtual GrammarElement * getGrammar(ParsedElement * f_parseTree) override + { + auto result = m_grammar.createElement(); + result->addChild(m_grammar.createElement("inject1")); + result->addChild(m_grammar.createElement("inject2")); + return result; + }; + + private: + Grammar & m_grammar; + +}; + +} diff --git a/src/libArgParse/Optional.hpp b/src/libArgParse/Optional.hpp new file mode 100644 index 0000000..5d6f8d7 --- /dev/null +++ b/src/libArgParse/Optional.hpp @@ -0,0 +1,83 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include + +namespace ArgParse +{ + +class Optional : public GrammarElement +{ + public: + Optional(const std::string & f_elementName = "") : + GrammarElement("Optional", f_elementName) + { + } + + virtual std::string toString() override + { + std::string result; + result += "["; + for(auto child: m_children) + { + result += child->toString(); + } + result += "]"; + return result; + } + + virtual ParseRc parse(const char * f_string, ParsedElement & f_out_ParsedElement, size_t candidateDepth = 1, size_t startChild = 0) override + { + ParseRc rc; + ParseRc childRc; + f_out_ParsedElement.setGrammarElement(this); + + GrammarElement * candidate = nullptr; + ParseRc candidateRc; + + auto child = m_children[0]; // FIXME: range check + auto newParsedElement = std::make_shared(&f_out_ParsedElement); + //printf("Optional start parse\n"); + childRc = child->parse(&f_string[rc.lenParsedSuccessfully], *newParsedElement); + //printf("Optional parse RC: "); + //childRc.print(); + //printf("\n"); + if(childRc.isGood()) + { + rc.lenParsedSuccessfully += childRc.lenParsedSuccessfully; + rc.lenParsed += childRc.lenParsed; + f_out_ParsedElement.addChild(newParsedElement); + } + if((not childRc.isGood()) && (childRc.errorType != ParseRc::ErrorType::unexpectedText)) + { + // add all candidates resulting from the child: + for(auto candidate : childRc.candidates) + { + //printf("add optional candidate : '%s'\n", candidate->getMatchedString().c_str()); + auto realCandidate = std::make_shared(f_out_ParsedElement.getParent()); + realCandidate->setGrammarElement(this); + realCandidate->setStops(); + realCandidate->addChild(candidate); + rc.candidates.push_back(realCandidate); + } + } + + rc.errorType = ParseRc::ErrorType::success; + + return rc; + } +}; + +} diff --git a/src/libArgParse/ParsedElement.hpp b/src/libArgParse/ParsedElement.hpp new file mode 100644 index 0000000..04049fd --- /dev/null +++ b/src/libArgParse/ParsedElement.hpp @@ -0,0 +1,180 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include +#include +#include + +namespace ArgParse +{ +class GrammarElement; +// a Tree +class ParsedElement +{ + public: + ParsedElement() : + m_grammarElement(nullptr), + m_parent(this) + { + } + + ParsedElement(ParsedElement * f_parent) : + m_grammarElement(nullptr), + m_parent(f_parent) + { + } + + ParsedElement(GrammarElement * f_grammarElement) : + m_grammarElement(f_grammarElement), + m_parent(this) + { + } + + GrammarElement * getGrammarElement() + { + return m_grammarElement; + } + + void setGrammarElement(GrammarElement * f_grammarElement) + { + m_grammarElement = f_grammarElement; + } + + ParsedElement & addChild(std::shared_ptr f_element) + { + f_element->setParent(this); + m_children.push_back(f_element); + return *(m_children.back()); + } + + void setMatchedString(const std::string & f_string) + { + m_matchedString = f_string; + } + + // prints the "flattened parse tree" i.e. the complete matched string. + std::string getMatchedString() const + { + std::string result = m_matchedString; + for(auto child : m_children) + { + result += child->getMatchedString(); + } + return result; + } + + // prints out the complete parse tree. + std::string getDebugString(std::string f_prefix = ""); + + std::vector > & getChildren() + { + return m_children; + } + + // depth first search for a single element, directly returning the matched string. + // @param f_elementName element name to search for (inherited from grammar element) + std::string findFirstChild(const std::string & f_elementName); + + // depth first search for a single element. + // @param f_elementName element name to search for (inherited from grammar element) + ParsedElement & findFirstSubTree(const std::string & f_elementName, bool & f_out_found); + + // depth first search for elements. + // @param f_elementName element name to search for (inherited from grammar element) + // @param f_out_result vector to which found elements are written to + // @param f_doNotSearchChildsOfMatchingElements if true, the search will + // not traverse deeper in a branch after finding the first match. + // example: . + // 1.A -> 2.B -> 3.B + // -> 4.C -> 5.B + // -> 6.D + // -> 7.B + // search for B with f_doNotSearchChildsOfMatchingElements == true: + // 2, 5, 7 + // search for B with f_doNotSearchChildsOfMatchingElements == false: + // 2, 3, 5, 7 + void findAllSubTrees(const std::string & f_elementName, std::vector & f_out_result, bool f_doNotSearchChildsOfMatchingElements = false); + + void setParent(ParsedElement * f_parent) + { + m_parent = f_parent; + } + + ParsedElement * getParent() + { + return m_parent; + } + + ParsedElement * getRoot() + { + ParsedElement * result = getParent(); + while(result->getParent() != result) + { + result = result->getParent(); + } + + return result; + } + + void setStops() + { + if(m_children.size() == 0) + { + m_stops = true; + } + else + { + + m_children.back()->setStops(); + } + } + bool isStopped() + { + if(m_stops) + { + return true; + } + for(auto child :m_children) + { + bool stopped = child->isStopped(); + if(stopped) + { + return true; + } + } + return false; + } + + void setIncompleteParse() + { + m_incompleteParse = true; + } + + bool isCompletelyParsed() + { + return (not m_incompleteParse); + } + + private: + GrammarElement * m_grammarElement; + ParsedElement * m_parent; + std::vector< std::shared_ptr > m_children; + bool m_stops = false; + std::string m_matchedString; + bool m_incompleteParse = false; +}; + +} diff --git a/src/libArgParse/RegEx.hpp b/src/libArgParse/RegEx.hpp new file mode 100644 index 0000000..d89d501 --- /dev/null +++ b/src/libArgParse/RegEx.hpp @@ -0,0 +1,127 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include + +#ifdef BUILD_CONFIG_USE_BOOST_REGEX + #include +#else + #include +#endif + +namespace ArgParse +{ + +// we use our own regex namespace here in which we provide regex types. +// Depending on build configuration these are either boost or standard library +// regex implementations +namespace regex +{ + #ifdef BUILD_CONFIG_USE_BOOST_REGEX + using boost::cmatch; + using boost::regex_search; + using boost::regex; + #else + using std::cmatch; + using std::regex_search; + using std::regex; + #endif +} + +class RegEx : public GrammarElement +{ + public: + + RegEx(const std::string f_regEx, std::string f_elementName = "") : + GrammarElement("RegEx", f_elementName), + m_regEx(f_regEx), + m_regExString(f_regEx) + { + } + + virtual std::string toString() override + { + std::string result; + if(m_elementName != "") + { + result = "/" + m_elementName + ":" + std::to_string(m_instanceId) + "/"; + } + else if(m_regExString != "") + { + result += "/"; + result += m_regExString; + result += "/"; + + } + else + { + result = m_typeName + "(UnnamedRegex:" + std::to_string(m_instanceId) + ")"; + } + return result; + } + + virtual ParseRc parse(const char * f_string, ParsedElement & f_out_ParsedElement, size_t candidateDepth = 1, size_t startChild = 0) override + { + ParseRc rc; + ParseRc childRc; + f_out_ParsedElement.setGrammarElement(this); + + //std::cmatch match; + regex::cmatch match; + if( + //std::regex_search(f_string, match, m_regEx) + regex::regex_search(f_string, match, m_regEx) + and + (match.position() == 0) + ) + { + // match has to be at the beginning + rc.errorType = ParseRc::ErrorType::success; + rc.lenParsedSuccessfully = match.length(); + rc.lenParsed = match.length(); + f_out_ParsedElement.setMatchedString(match[0]); + //printf("regex %u /%s/ did match\n", m_instanceId, m_regExString.c_str()); + } + else + { + //printf("regex %u /%s/ did not match\n", m_instanceId, m_regExString.c_str()); + rc.lenParsedSuccessfully = 0; + rc.lenParsed = strlen(f_string); + if(strlen(f_string) == 0) + { + //printf("regex %u /%s/ have missing text\n", m_instanceId, m_regExString.c_str()); + rc.errorType = ParseRc::ErrorType::missingText; + } + else + { + rc.errorType = ParseRc::ErrorType::unexpectedText; + } + } + + return rc; + } + virtual std::string getDotNode() override + { + std::string result = ""; + result += "n" + std::to_string(m_instanceId) + "[label=\"" + std::to_string(m_instanceId) + " " + m_typeName + " " + m_elementName + " " + m_tag + "'" + m_regExString + "'\"];\n"; + return result; + } + private: + //const std::regex m_regEx; + const regex::regex m_regEx; + const std::string m_regExString; +}; + +} diff --git a/src/libArgParse/Repetition.hpp b/src/libArgParse/Repetition.hpp new file mode 100644 index 0000000..846d7d9 --- /dev/null +++ b/src/libArgParse/Repetition.hpp @@ -0,0 +1,126 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include + +namespace ArgParse +{ +class Repetition : public GrammarElement +{ + public: + Repetition(const std::string & f_elementName = ""): + GrammarElement("Repetition", f_elementName) + { + } + + virtual std::string toString() override + { + std::string result; + result += "("; + result += m_children[0]->toString(); + result += ")*"; + return result; + } + + virtual ParseRc parse(const char * f_string, ParsedElement & f_out_ParsedElement, size_t candidateDepth = 1, size_t startChild = 0) override + { + //printf("rep parse\n"); + ParseRc rc; + ParseRc childRc; + f_out_ParsedElement.setGrammarElement(this); + + GrammarElement * candidate = nullptr; + ParseRc candidateRc; + + GrammarElement * child = nullptr; + if(m_children.size() > 0) + { + child = m_children[0]; + } + std::vector> successfullyParsedChilds; + bool overParsed = false; + while(childRc.isGood() && (child != nullptr) ) + { + if(childRc.isGood() && (f_string[rc.lenParsedSuccessfully] == '\0')) + { + // we parsed successfullt and exactly aligned with the end :) + // we continue parsing once more, to get possible further candidates + // then we end + // we set this flag here, to remember to switch the RC to success + overParsed = true; + } + auto newParsedElement = std::make_shared(&f_out_ParsedElement); + //printf("Optional start parse\n"); + childRc = child->parse(&f_string[rc.lenParsedSuccessfully], *newParsedElement); + //std::cout << " Rep "<< std::to_string(m_instanceId) << " parsed child. rc=" << childRc.toString() << std::endl; + //printf("Optional parse RC: "); + //childRc.print(); + //printf("\n"); + if(childRc.isGood() || childRc.errorType == ParseRc::ErrorType::missingText) + { + rc.lenParsedSuccessfully += childRc.lenParsedSuccessfully; + rc.lenParsed += childRc.lenParsed; + f_out_ParsedElement.addChild(newParsedElement); + } + if(childRc.isGood()) + { + // TODO: make unittest where those lists are different (partial parse should only complete partial result, but not append to successful list completions) + successfullyParsedChilds.push_back(newParsedElement); + } + } + // FIXME: maybe we have to move this in the loop and also process good rcs here with candidates (from optional/repetition/etc.) similarly to concat + if((not childRc.isGood()) && (childRc.errorType != ParseRc::ErrorType::unexpectedText)) + { + // add all candidates resulting from the child: + for(auto candidate : childRc.candidates) + { + //std::cout << "Rep " << std::to_string(m_instanceId) << " handling candidate '" << candidate->getMatchedString() << "'" << std::endl; + //printf("add optional candidate : '%s'\n", candidate->getMatchedString().c_str()); + auto realCandidate = std::make_shared(f_out_ParsedElement.getParent()); + realCandidate->setGrammarElement(this); + realCandidate->setStops(); // think about this is this required for repetition? + // add all previous childs (similar to concatenation): + for(auto previousChild : successfullyParsedChilds) + { + realCandidate->addChild(previousChild); + } + realCandidate->addChild(candidate); + //std::cout << " Rep "<< std::to_string(m_instanceId) << " add candidate '" << realCandidate->getMatchedString() << "'" << std::endl; + rc.candidates.push_back(realCandidate); + } + if(overParsed) + { + // if we are at the end of the string we return no error + // -> why?? + rc.errorType = ParseRc::ErrorType::success; + } + else + { + // otherwise we still have missing text + rc.errorType = ParseRc::ErrorType::missingText; + } + } + else + { + // if we saw something completely wrong, we declare us as success (we also allow zero matches) + rc.errorType = ParseRc::ErrorType::success; + } + + // FIXME empty string is also a candidate!! + return rc; + } +}; + +} diff --git a/src/libArgParse/WhiteSpace.hpp b/src/libArgParse/WhiteSpace.hpp new file mode 100644 index 0000000..730541c --- /dev/null +++ b/src/libArgParse/WhiteSpace.hpp @@ -0,0 +1,88 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +namespace ArgParse +{ +class WhiteSpace : public GrammarElement +{ + public: + WhiteSpace() : + GrammarElement("WhiteSpace") + { + } + + virtual std::string toString() override + { + return " "; + } + + virtual ParseRc parse(const char * f_string, ParsedElement & f_out_ParsedElement, size_t candidateDepth = 1, size_t startChild = 0) override + { + ParseRc rc; + ParseRc childRc; + f_out_ParsedElement.setGrammarElement(this); + + std::string matchedString = ""; + size_t i; + for(i = 0; i< strlen(f_string); i++) + { + if(f_string[i] == ' ') + { + matchedString += f_string[i]; + } + else + { + break; + } + } + //printf("comparing: '%s' == '%s'\n", f_string, m_string.c_str()); + if(matchedString != "") + { + //printf(" -> same\n"); + rc.errorType = ParseRc::ErrorType::success; + rc.lenParsedSuccessfully = i; + rc.lenParsed = i; + f_out_ParsedElement.setMatchedString(matchedString); + } + else + { + rc.lenParsedSuccessfully = 0; + if(i == strlen(f_string)) + { + rc.lenParsed = strlen(f_string); + // have a candidate for completion :) + //printf(" -> completion possible\n"); + // create a candidate: + auto candidate = std::make_shared(&f_out_ParsedElement); + candidate->setGrammarElement(this); + candidate->setMatchedString(" "); + rc.candidates.push_back(candidate); + + // set rc + rc.errorType = ParseRc::ErrorType::missingText; + } + else + { + //printf(" -> totally different\n"); + rc.errorType = ParseRc::ErrorType::unexpectedText; + } + } + + return rc; + } +}; + +} diff --git a/src/libCli/CMakeLists.txt b/src/libCli/CMakeLists.txt new file mode 100644 index 0000000..1c9461b --- /dev/null +++ b/src/libCli/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8) + +set(TARGET_NAME "cli") +set(TARGET_SRC + ./MessageParsing.cpp + ./OutputFormatting.cpp + ./GrammarConstruction.cpp + ./Completion.cpp + ./Call.cpp + ) +add_library(${TARGET_NAME} ${TARGET_SRC}) +target_link_libraries ( ${TARGET_NAME} + reflection + ArgParse + ) + +if(BUILD_CONFIG_USE_BOOST_REGEX) + target_link_libraries (${TARGET_NAME} + boost_regex + ) +endif() diff --git a/src/libCli/Call.cpp b/src/libCli/Call.cpp new file mode 100644 index 0000000..c266802 --- /dev/null +++ b/src/libCli/Call.cpp @@ -0,0 +1,301 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// for detecting if we are writing stdout to terminal or to pipe/file +#include +#include + +using namespace ArgParse; + +namespace cli +{ + +// TODO: move this to OutputFormatting code +std::string customMessageFormat(const grpc::protobuf::Message & f_message, const grpc::protobuf::Descriptor* f_messageDescriptor, ParsedElement & f_customFormatParseTree, size_t startChild = 0) +{ + std::string result; + const google::protobuf::Reflection * reflection = f_message.GetReflection(); + + // first look for target fields to format: + bool found = false; + ParsedElement targetList = f_customFormatParseTree.findFirstSubTree("TargetSpecifier", found); + + if((targetList.getChildren().size() > startChild) && (targetList.getChildren()[startChild] != nullptr)) + { + std::string partialTarget = targetList.getChildren()[startChild]->findFirstChild("PartialTarget"); + //std::cout << "looking at '" << partialTarget << "'\n"; + if(partialTarget != "") + { + // empty target addresses the current message + const google::protobuf::FieldDescriptor * partialField = f_messageDescriptor->FindFieldByName(partialTarget); + if(partialField == nullptr) + { + return "No such field: " + partialTarget; + } + + // now we have three possibilities: + // 1. repeated field + // -> iterate over all instances + call recursive + return + // 2. message type field + // -> call recursive + return + // 3. normal field (terminal) + // -> continue looping + + if(partialField->is_repeated()) + { + //std::cout << "have repeated\n"; + switch(partialField->type()) + { + case grpc::protobuf::FieldDescriptor::Type::TYPE_MESSAGE: + { + + } + break; + default: + return "repeated-" + std::string(partialField->type_name()) + " is not yet supported :(\n"; + break; + } + int numberOfRepetitions = reflection->FieldSize(f_message, partialField); + for(int j = 0; j < numberOfRepetitions; j++) + { + //std::cout << " have repeated entry\n"; + const google::protobuf::Message & subMessage = reflection->GetRepeatedMessage(f_message, partialField, j); + result += customMessageFormat(subMessage, partialField->message_type(), f_customFormatParseTree, startChild+1); + } + return result; + } + if(partialField->type() == grpc::protobuf::FieldDescriptor::Type::TYPE_MESSAGE) + { + //std::cout << "have message\n"; + const google::protobuf::Message & subMessage = reflection->GetMessage(f_message, partialField); + return customMessageFormat(subMessage, partialField->message_type(), f_customFormatParseTree, startChild+1); + } + } + } + + // now we know we are not repeated and now f_message contains the correct + // context in which to evaluate field references :) + + //std::cout << "have field\n"; + OutputFormatter myOutputFormatter; + myOutputFormatter.clearColorMap(); + + bool haveFormatString = false; + auto formatString = f_customFormatParseTree.findFirstSubTree("OutputFormatString", haveFormatString); + if(not haveFormatString) + { + return "Error: no format string given\n"; + } + for(auto outputStatement : formatString.getChildren()) + { + + bool foundFieldReference = false; + auto fieldReference = outputStatement->findFirstSubTree("OutputFieldReference", foundFieldReference); + if(foundFieldReference) + { + //std::cout << " have field ref " << fieldReference.getMatchedString() << "\n"; + // need to lookup the field: + const google::protobuf::FieldDescriptor * fieldRef = f_messageDescriptor->FindFieldByName(fieldReference.getMatchedString()); + if(fieldRef == nullptr) + { + result += "???"; + } + else + { + result += myOutputFormatter.fieldValueToString(f_message, fieldRef, "", "", OutputFormatter::CustomStringModifier::Raw); + } + } + else + { + //std::cout << " have string " << outputStatement->getMatchedString() << "\n"; + result += outputStatement->getMatchedString(); + } + } + + return result; +} + +std::string getTimeString() +{ + // unfortunately std::chrono::system_clock::to_time_t() is not available + // with gcc4.8. So we use std::time and std::strftime instead. + std::time_t t = std::time(0) ; + char cstr[128] ; + std::strftime( cstr, sizeof(cstr), "%Y-%m-%d %X", std::localtime(&t) ) ; + return cstr ; +} + +int call(ParsedElement & parseTree) +{ + std::string serverAddress = parseTree.findFirstChild("ServerAddress"); + std::string serverPort = parseTree.findFirstChild("ServerPort"); + if(serverPort == "") + { + serverPort = "50051"; + } + serverAddress += ":" + serverPort; + + std::string serviceName = parseTree.findFirstChild("Service"); + std::string methodName = parseTree.findFirstChild("Method"); + bool argsExist; + ParsedElement & methodArgs = parseTree.findFirstSubTree("MethodArgs", argsExist); + + std::shared_ptr channel = + grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()); + + + grpc::ProtoReflectionDescriptorDatabase descDb(channel); + grpc::protobuf::DescriptorPool descPool(&descDb); + + const grpc::protobuf::ServiceDescriptor* service = descPool.FindServiceByName(serviceName); + if(service == nullptr) + { + std::cerr << "Error: Service '" << serviceName << "' not found" << std::endl; + return -1; + } + + auto method = service->FindMethodByName(methodName); + if(method == nullptr) + { + std::cerr << "Error: Method not found" << std::endl; + return -1; + } + + if(method->client_streaming()) + { + std::cerr << "Error: Client streaming RPCs not supported." << std::endl; + return -1; + } + + const grpc::protobuf::Descriptor* inputType = method->input_type(); + + // now we have to construct a protobuf from the parsed argument, which corresponds to the inputType + google::protobuf::DynamicMessageFactory dynamicFactory; + + // read data from the parse tree into the protobuf message: + std::unique_ptr message = cli::parseMessage(parseTree, dynamicFactory, inputType); + + + + if(parseTree.findFirstChild("PrintParsedMessage") != "") + { + // use built-in human readable output format + cli::OutputFormatter imessageFormatter; + std::cout << "Request message:" << std::endl << imessageFormatter.messageToString(*message, method->input_type(), "| ", "| " ) << std::endl; + } + + + if(not message) + { + std::cerr << "Error: Error parsing method arguments -> aborting the call :-(" << std::endl; + return -1; + } + + // now we serialize the message: + grpc::string serializedRequest; + bool success = message->SerializeToString(&serializedRequest); + if(not success) + { + std::cerr << "Error: Failed to serialize method arguments" << std::endl; + return -1; + } + + // now we do the actual RPC call: + std::multimap clientMetadata; + grpc::string serializedResponse; + std::multimap serverMetadataA; + std::multimap serverMetadataB; + + std::string methodStr = "/" + serviceName + "/" + methodName; + grpc::testing::CliCall call(channel, methodStr, clientMetadata); + call.Write(serializedRequest); + call.WritesDone(); + + // In a loop we read reply data from the reply stream: + // NOTE: in gRPC every RPC can be considered "streaming". Non-streaming RPCs + // merely return one reply message. + bool init = true; + for (init = true; call.Read(&serializedResponse, init ? &serverMetadataA : nullptr); init= false) + { + // convert data received from stream into a message: + std::unique_ptr replyMessage(dynamicFactory.GetPrototype(method->output_type())->New()); + replyMessage->ParseFromString(serializedResponse); + + // print date/time of message reception: + std::cout << getTimeString(); + std::cout << ": Received message:\n"; + + // print out string representation of the message: + std::string msgString; + + // decide on message formatting method to use: + bool customOutputFormatRequested = false; + ParsedElement customFormatParseTree = parseTree.findFirstSubTree("CustomOutputFormat", customOutputFormatRequested); + if(not customOutputFormatRequested) + { + // use built-in human readable output format + cli::OutputFormatter messageFormatter; + + // disable colored output if explicitly specified: + if(parseTree.findFirstChild("NoColor") != "") + { + messageFormatter.clearColorMap(); + } + + // automatically disable colored output, when outputting to something + // else than a terminal (pipes, files, etc.), except we explicitly + // request color mode: + if((not isatty(fileno(stdout))) and (parseTree.findFirstChild("Color") == "")) + { + messageFormatter.clearColorMap(); + } + + msgString = messageFormatter.messageToString(*replyMessage, method->output_type(), "| ", "| " ); + } + else + { + //std::cout << "using custom OutputFormatting\n"; + //std::cout << customFormatParseTree.getDebugString(); + // use user provided output format string + msgString = customMessageFormat(*replyMessage, method->output_type(), customFormatParseTree); + } + std::cout << msgString << std::endl; + } + + // reply stream finished -> finish the RPC: + grpc::Status status = call.Finish(&serverMetadataB); + + if(not status.ok()) + { + std::cerr << "RPC failed ;( Status code: " << std::to_string(status.error_code()) << ", error message: " << status.error_message() << std::endl; + return -1; + } + + std::cout << "RPC succeeded :D" << std::endl; + + + return 0; +} + +} diff --git a/src/libCli/Call.hpp b/src/libCli/Call.hpp new file mode 100644 index 0000000..b8aac51 --- /dev/null +++ b/src/libCli/Call.hpp @@ -0,0 +1,25 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +namespace cli +{ + /// Performs an RPC call based on information from the parse tree. + /// @param f_parseTree Parse tree containing all relevant information for the call (server address, request message, options, ...). + /// @returns 0 if RPC succeeded, -1 otherwise (including parse errors from parse tree and gRPC bad return code) + int call(ArgParse::ParsedElement & f_parseTree); +} diff --git a/src/libCli/Completion.cpp b/src/libCli/Completion.cpp new file mode 100644 index 0000000..185c4f3 --- /dev/null +++ b/src/libCli/Completion.cpp @@ -0,0 +1,91 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +using namespace ArgParse; + +namespace cli +{ + +void printBashCompletions( std::vector > & f_candidates, ParsedElement & f_parseTree, const std::string & f_args, bool f_debug) +{ + // completion requested :) + if(f_debug) + { + std::cerr << "Input string \"" << f_args << "\"\nCandidates:\n" << std::endl; + size_t n = f_parseTree.getMatchedString().size(); + for(auto candidate : f_candidates) + { + std::string candidateStr =candidate->getMatchedString(); + printf("pre: '%s'\n", candidateStr.c_str()); + } + } + + //size_t n = parseTree.getMatchedString().size(); + size_t n = f_args.size(); + for(auto candidate : f_candidates) + { + std::string candidateStr =candidate->getMatchedString(); + std::string suggestion; + size_t start = n; + size_t end; + if(f_debug) + { + printf("candidateStr[n=%zu] = '%c'\n", n, candidateStr[n]); + } + if( + (candidateStr[n] != ' ') + && + (candidateStr[n] != '=') + && + (candidateStr[n] != ',') + && + (candidateStr[n] != ':') + ) + { + // bash always expects completion suggestions to start from the last token. + // Now we need to find out where the last token has started + // We need to "simulate" bash tokenizer here. tokens are delimited by ' ' '=' or ':' + start = candidateStr.find_last_of(" =:,", n)+1; + if(start == std::string::npos) + { + start = 0; + } + end = candidateStr.find_first_of(" ", n)-1; + //printf("cand='%s', n=%zu, start=%zu, end = %zu\n",candidateStr.c_str(), n, start, end); + } + else + { + start = candidateStr.find_last_of(" =:", n-1)+1; + //end = candidateStr.find_first_of(' ', n+1); + end = n; + } + //printf("start=%zu, end=%zu\n", start, end); + //suggestion = candidateStr.substr(n, std::string::npos); + suggestion = candidateStr.substr(start, std::string::npos); + //suggestion = candidateStr.substr(start, end-start+1); + if(f_debug) + { + + printf("post: '%s'\n", suggestion.c_str()); + } + else + { + printf("%s\n", suggestion.c_str()); + } + } +} + +} diff --git a/src/libCli/Completion.hpp b/src/libCli/Completion.hpp new file mode 100644 index 0000000..2a738b8 --- /dev/null +++ b/src/libCli/Completion.hpp @@ -0,0 +1,35 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include +#include + +namespace cli +{ + /// Function which prints bash completions to stdout for given list of parseTrees. + /// NOTE: this is not calculationg completions, it merely formats existing completion results + /// in a way, so that bash can handle them. + /// @param f_candidates vector of parse trees, each representing a completion candidate + /// @param f_parseTree the parstree which contains everything which could already be matched. + /// @param f_args the string given by the user which awaits completion + /// @param f_debug enables debug output if true + void printBashCompletions( + std::vector > & f_candidates, + ArgParse::ParsedElement & f_parseTree, + const std::string & f_args, + bool f_debug + ); +} diff --git a/src/libCli/GrammarConstruction.cpp b/src/libCli/GrammarConstruction.cpp new file mode 100644 index 0000000..d8faadd --- /dev/null +++ b/src/libCli/GrammarConstruction.cpp @@ -0,0 +1,455 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +using namespace ArgParse; + +namespace cli +{ + +class GrammarInjectorMethodArgs : public GrammarInjector +{ + public: + GrammarInjectorMethodArgs(Grammar & f_grammar, const std::string & f_elementName = "") : + GrammarInjector("MethodArgs", f_elementName), + m_grammar(f_grammar) + { + } + + virtual ~GrammarInjectorMethodArgs() + { + } + + virtual GrammarElement * getGrammar(ParsedElement * f_parseTree) override + { + // FIXME: we are already completing this without a service parsed. + // this works in most cases, as it will just fail. however this is not really a nice thing. + std::string serverAddress = f_parseTree->findFirstChild("ServerAddress"); + std::string serverPort = f_parseTree->findFirstChild("ServerPort"); + std::string serviceName = f_parseTree->findFirstChild("Service"); + std::string methodName = f_parseTree->findFirstChild("Method"); + + //std::cout << f_parseTree->getDebugString() << std::endl; + //std::cout << "Injecting grammar for " << serverAddress << ":" << serverPort << " " << serviceName << " " << methodName << std::endl; + if(serverPort == "") + { + serverPort = "50051"; + } + serverAddress += ":" + serverPort; + //std::cout << "Server addr: " << serverAddress << std::endl; + std::shared_ptr channel = + grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()); + + + grpc::ProtoReflectionDescriptorDatabase descDb(channel); + grpc::protobuf::DescriptorPool descPool(&descDb); + + const grpc::protobuf::ServiceDescriptor* service = descPool.FindServiceByName(serviceName); + if(service == nullptr) + { + //std::cerr << "Error: Service not found" << std::endl; + return m_grammar.createElement(""); + } + + auto method = service->FindMethodByName(methodName); + if(method == nullptr) + { + //std::cerr << "Error: Method not found" << std::endl; + return m_grammar.createElement(""); + } + + if(method->client_streaming()) + { + std::cerr << "Error: Client streaming RPCs not supported." << std::endl; + return m_grammar.createElement(""); + } + + auto concat = m_grammar.createElement(); + + auto separation = m_grammar.createElement(); + //auto separation = m_grammar.createElement(); + //separation->addChild(m_grammar.createElement()); + //separation->addChild(m_grammar.createElement(",")); + concat->addChild(separation); + + auto fields = getMessageGrammar(method->input_type()); + concat->addChild(fields); + + auto result = m_grammar.createElement("Fields"); + result->addChild(concat); + + return result; + }; + + private: + + void addFieldValueGrammar(GrammarElement * f_fieldGrammar, const grpc::protobuf::FieldDescriptor * f_field) + { + switch(f_field->cpp_type()) + { + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_FLOAT: + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_DOUBLE: + // TODO: make regex match closer + f_fieldGrammar->addChild(m_grammar.createElement("[\\+-\\.pP0-9a-fA-F]+", "FieldValue")); + break; + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_INT32: + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_INT64: + f_fieldGrammar->addChild(m_grammar.createElement("[\\+-]?(0x|0b)?[0-9a-fA-F]+", "FieldValue")); + break; + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_UINT32: + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_UINT64: + f_fieldGrammar->addChild(m_grammar.createElement("\\+?(0x|0b)?[0-9a-fA-F]+", "FieldValue")); + break; + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_BOOL: + { + auto boolGrammar = m_grammar.createElement("FieldValue"); + boolGrammar->addChild(m_grammar.createElement("true")); + boolGrammar->addChild(m_grammar.createElement("false")); + boolGrammar->addChild(m_grammar.createElement("1")); + boolGrammar->addChild(m_grammar.createElement("0")); + f_fieldGrammar->addChild(boolGrammar); + break; + } + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_ENUM: + { + const google::protobuf::EnumDescriptor * enumDesc = f_field->enum_type(); + auto enumGrammar = m_grammar.createElement("FieldValue"); + for(int i = 0; ivalue_count(); i++) + { + const google::protobuf::EnumValueDescriptor * enumValueDesc = enumDesc->value(i); + // FIXME: null possible? + enumGrammar->addChild(m_grammar.createElement(enumValueDesc->name())); + } + f_fieldGrammar->addChild(enumGrammar); + break; + } + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_STRING: + if(f_field->type() == grpc::protobuf::FieldDescriptor::Type::TYPE_BYTES) + { + auto bytesContainer = m_grammar.createElement("FieldValue"); + bytesContainer->addChild(m_grammar.createElement("0x")); + bytesContainer->addChild(m_grammar.createElement("[0-9a-fA-F]*", "")); + f_fieldGrammar->addChild(bytesContainer); + } + else + { + // FIXME: commented solution does not work: + // we always complete "::" as empty string matches + // Solution would be to change the parser to never + // attempt completion when a regex is currently + // parsed. This requires changes in the parser + // and could not be implemented on short notice. + //auto stringContainer = m_grammar.createElement("StringContainer"); + //stringContainer->addChild(m_grammar.createElement(":")); + //stringContainer->addChild(m_grammar.createElement("[^:]*", "FieldValue")); + //stringContainer->addChild(m_grammar.createElement(":")); + //f_fieldGrammar->addChild(stringContainer); + + // Using this as a workaround until parser gets better regex support + f_fieldGrammar->addChild(m_grammar.createElement("[^ ]*", "FieldValue")); + } + break; + case grpc::protobuf::FieldDescriptor::CppType::CPPTYPE_MESSAGE: + { + //std::cerr << "Field '" << field->name() << "' has message type: '" << field->type_name() << "'" << std::endl; + auto subMessage = m_grammar.createElement("FieldValue"); + subMessage->addChild(m_grammar.createElement(":")); + + auto childFieldsRep = m_grammar.createElement("Fields"); + auto concat = m_grammar.createElement(); + auto fieldsAlt = getMessageGrammar(f_field->message_type()); + concat->addChild(fieldsAlt); + + auto separation = m_grammar.createElement(); + //auto separation = m_grammar.createElement(); + //separation->addChild(m_grammar.createElement()); + //separation->addChild(m_grammar.createElement(",")); + concat->addChild(separation); + + childFieldsRep->addChild(concat); + subMessage->addChild(childFieldsRep); + + subMessage->addChild(m_grammar.createElement(":")); + f_fieldGrammar->addChild(subMessage); + break; + } + default: + std::cerr << "Field '" << f_field->name() << "' has unsupported type: '" << f_field->type_name() << "'" << std::endl; + break; + } + } + + GrammarElement * getMessageGrammar(const grpc::protobuf::Descriptor* f_messageDescriptor) + { + auto fields = m_grammar.createElement(); + // iterate over fields: + for(int i = 0; i< f_messageDescriptor->field_count(); i++) + { + const grpc::protobuf::FieldDescriptor * field = f_messageDescriptor->field(i); + + //std::cerr << "Iterating field " << std::to_string(i) << " of message " << f_messageDescriptor->name() << "with name: '" << field->name() <<"'"<< std::endl; + + // now we add grammar to the fields alternation: + auto fieldGrammar = m_grammar.createElement(); + fieldGrammar->addChild(m_grammar.createElement(field->name(), "FieldName")); + fieldGrammar->addChild(m_grammar.createElement("=")); + fields->addChild(fieldGrammar); + if(field->is_repeated()) + { + auto repeatedValue = m_grammar.createElement("RepeatedValue"); + addFieldValueGrammar(repeatedValue, field); + + auto repeatedGrammar = m_grammar.createElement("FieldValue"); + repeatedGrammar->addChild(m_grammar.createElement(":")); + + repeatedGrammar->addChild(repeatedValue); + + auto repeatedOptionalEntry = m_grammar.createElement(); + repeatedOptionalEntry->addChild(m_grammar.createElement(",")); + repeatedOptionalEntry->addChild(m_grammar.createElement()); + repeatedOptionalEntry->addChild(repeatedValue); + + auto repeatedOptionalValues = m_grammar.createElement(); + repeatedOptionalValues->addChild(repeatedOptionalEntry); + repeatedGrammar->addChild(repeatedOptionalValues); + repeatedGrammar->addChild(m_grammar.createElement(":")); + + + fieldGrammar->addChild(repeatedGrammar); + } + else + { + // the simple case: + addFieldValueGrammar(fieldGrammar, field); + } + } + + //std::cout << "Grammar generated:\n" << fields->toString() << std::endl; + return fields; + } + + + + Grammar & m_grammar; + +}; + +class GrammarInjectorMethods : public GrammarInjector +{ + public: + GrammarInjectorMethods(Grammar & f_grammar, const std::string & f_elementName = "") : + GrammarInjector("Method", f_elementName), + m_grammar(f_grammar) + { + } + + virtual GrammarElement * getGrammar(ParsedElement * f_parseTree) override + { + // FIXME: we are already completing this without a service parsed. + // this works in most cases, as it will just fail. however this is not really a nice thing. + std::string serverAddress = f_parseTree->findFirstChild("ServerAddress"); + std::string serverPort = f_parseTree->findFirstChild("ServerPort"); + std::string serviceName = f_parseTree->findFirstChild("Service"); + + //std::cout << f_parseTree->getDebugString() << std::endl; + //std::cout << "Injecting grammar for " << serverAddress << ":" << serverPort << " " << serviceName << std::endl; + if(serverPort == "") + { + serverPort = "50051"; + } + serverAddress += ":" + serverPort; + //std::cout << "Server addr: " << serverAddress << std::endl; + std::shared_ptr channel = + grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()); + + + grpc::ProtoReflectionDescriptorDatabase descDb(channel); + grpc::protobuf::DescriptorPool descPool(&descDb); + + const grpc::protobuf::ServiceDescriptor* service = descPool.FindServiceByName(serviceName); + + auto result = m_grammar.createElement(); + if(service != nullptr) + { + for (int i = 0; i < service->method_count(); ++i) + { + result->addChild(m_grammar.createElement(service->method(i)->name())); + } + } + return result; + }; + + private: + Grammar & m_grammar; + +}; + +class GrammarInjectorServices : public GrammarInjector +{ + public: + GrammarInjectorServices(Grammar & f_grammar, const std::string & f_elementName = "") : + GrammarInjector("Service", f_elementName), + m_grammar(f_grammar) + { + } + + virtual ~GrammarInjectorServices() + { + } + + virtual GrammarElement * getGrammar(ParsedElement * f_parseTree) override + { + std::string serverAddress = f_parseTree->findFirstChild("ServerAddress"); + std::string serverPort = f_parseTree->findFirstChild("ServerPort"); + if(serverPort == "") + { + serverPort = "50051"; + } + serverAddress += ":" + serverPort; + //std::cout << "Server addr: " << serverAddress << std::endl; + std::shared_ptr channel = + grpc::CreateChannel(serverAddress, grpc::InsecureChannelCredentials()); + + + grpc::ProtoReflectionDescriptorDatabase descDb(channel); + //grpc::protobuf::DescriptorPool desc_pool(&desc_db); + + std::vector serviceList; + if(not descDb.GetServices(&serviceList) ) + { + printf("error retrieving service list\n"); + //return -1; + } + + auto result = m_grammar.createElement(); + for(auto service : serviceList) + { + + result->addChild(m_grammar.createElement(service)); + } + return result; + }; + + private: + Grammar & m_grammar; + +}; + +GrammarElement * constructGrammar(Grammar & f_grammarPool) +{ + // user defined output formatting + // something like this will match: @.fru_info_list:found fru in slot /slot_id/: + GrammarElement * formatTargetSpecifier = f_grammarPool.createElement("CustomOutputFormat"); + formatTargetSpecifier->addChild(f_grammarPool.createElement("@")); + GrammarElement * formatTargetTree = f_grammarPool.createElement("TargetSpecifier"); + GrammarElement * formatTargetPart = f_grammarPool.createElement(); + formatTargetPart->addChild(f_grammarPool.createElement(".")); + formatTargetPart->addChild(f_grammarPool.createElement("[^:]*", "PartialTarget")); + formatTargetTree->addChild(formatTargetPart); + formatTargetSpecifier->addChild(formatTargetTree); + formatTargetSpecifier->addChild(f_grammarPool.createElement(":")); + GrammarElement * formatOutputSpecifier = f_grammarPool.createElement("OutputFormatString"); + GrammarElement * formatOutputSpecifierAlternation = f_grammarPool.createElement(); + formatOutputSpecifierAlternation->addChild(f_grammarPool.createElement("[^:/]+", "OutputFixedString")); // a real string + GrammarElement * fieldReference = f_grammarPool.createElement(); + fieldReference->addChild(f_grammarPool.createElement("/")); + fieldReference->addChild(f_grammarPool.createElement("[^/,]*", "OutputFieldReference")); // field reference + GrammarElement * modifiers = f_grammarPool.createElement("OutputFormatModifiers"); + GrammarElement * modifierConcat = f_grammarPool.createElement(); + modifierConcat->addChild(f_grammarPool.createElement(",")); + GrammarElement * modifierAlternation = f_grammarPool.createElement("Modifier"); + modifierAlternation->addChild(f_grammarPool.createElement("hex")); + modifierAlternation->addChild(f_grammarPool.createElement("dec")); + modifierAlternation->addChild(f_grammarPool.createElement("zeroPadding")); + modifierAlternation->addChild(f_grammarPool.createElement("spacePadding")); + modifierAlternation->addChild(f_grammarPool.createElement("noPadding")); + modifiers->addChild(modifierConcat); + modifierConcat->addChild(modifierAlternation); + fieldReference->addChild(modifiers); + fieldReference->addChild(f_grammarPool.createElement("/")); + formatOutputSpecifierAlternation->addChild(fieldReference); + formatOutputSpecifier->addChild(formatOutputSpecifierAlternation); + + formatTargetSpecifier->addChild(formatOutputSpecifier); + formatTargetSpecifier->addChild(f_grammarPool.createElement(":")); + + GrammarElement * customOutputFormat = f_grammarPool.createElement(); + customOutputFormat->addChild(f_grammarPool.createElement("--customOutput")); + customOutputFormat->addChild(f_grammarPool.createElement()); + customOutputFormat->addChild(formatTargetSpecifier); + // TODO add this to options + + // options + GrammarElement * options = f_grammarPool.createElement(); // TODO: support multiple options + GrammarElement * optionsconcat = f_grammarPool.createElement(); + GrammarElement * optionsalt = f_grammarPool.createElement(); + optionsalt->addChild(f_grammarPool.createElement("-h", "Help")); + optionsalt->addChild(f_grammarPool.createElement("--help", "Help")); + optionsalt->addChild(f_grammarPool.createElement("--complete", "Complete")); + optionsalt->addChild(f_grammarPool.createElement("--debugComplete", "CompleteDebug")); + optionsalt->addChild(f_grammarPool.createElement("--dot", "DotExport")); + optionsalt->addChild(f_grammarPool.createElement("--noColor", "NoColor")); + optionsalt->addChild(f_grammarPool.createElement("--color", "Color")); + optionsalt->addChild(f_grammarPool.createElement("--version", "Version")); + optionsalt->addChild(f_grammarPool.createElement("--printParsedMessage", "PrintParsedMessage")); + optionsalt->addChild(customOutputFormat); + // FIXME FIXME FIXME: we cannot distinguish between --complete and --completeDebug.. this is a problem for arguments too, as we cannot guarantee, that we do not have an argument starting with the name of an other argument. + // -> could solve by makeing FixedString greedy + optionsconcat->addChild(optionsalt); + optionsconcat->addChild( + f_grammarPool.createElement() + ); + //optionsconcat->addChild( + // f_grammarPool.createElement()->addChild( + // f_grammarPool.createElement() + // ) + // ); + options->addChild(optionsconcat); + + // Server address + GrammarElement * serverAddress = f_grammarPool.createElement("ServerAddress"); + serverAddress->addChild(f_grammarPool.createElement("\\d+\\.\\d+\\.\\d+\\.\\d+", "IPv4Address")); + serverAddress->addChild(f_grammarPool.createElement("\\[?[0-9a-fA-F:]+\\]?", "IPv6Address")); + serverAddress->addChild(f_grammarPool.createElement("[^\\.:\\[\\] ]+", "Hostname")); + + // Server port + GrammarElement * cServerPort = f_grammarPool.createElement(); + cServerPort->addChild(f_grammarPool.createElement(":")); + cServerPort->addChild(f_grammarPool.createElement("\\d+", "ServerPort")); + GrammarElement * serverPort = f_grammarPool.createElement(); + serverPort->addChild(cServerPort); + + //GrammarElement * testAlt = f_grammarPool.createElement("TestAlt"); + //testAlt->addChild(f_grammarPool.createElement("challo")); + //testAlt->addChild(f_grammarPool.createElement("ctschuess")); + + // main concat: + GrammarElement * cmain = f_grammarPool.createElement(); + cmain->addChild(options); + cmain->addChild(serverAddress); + cmain->addChild(serverPort); + //cmain->addChild(testAlt); + //cmain->addChild(f_grammarPool.createElement(std::regex("\\S+"), "ServerAddress")); + cmain->addChild(f_grammarPool.createElement()); + cmain->addChild(f_grammarPool.createElement(f_grammarPool, "Service")); + cmain->addChild(f_grammarPool.createElement()); + cmain->addChild(f_grammarPool.createElement(f_grammarPool, "Method")); + //cmain->addChild(f_grammarPool.createElement()); + cmain->addChild(f_grammarPool.createElement(f_grammarPool, "MethodArgs")); + + return cmain; +} +} diff --git a/src/libCli/GrammarConstruction.hpp b/src/libCli/GrammarConstruction.hpp new file mode 100644 index 0000000..664eb56 --- /dev/null +++ b/src/libCli/GrammarConstruction.hpp @@ -0,0 +1,26 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +namespace cli +{ + /// Constructs the grammar for the gWhisper CLI. + /// @param f_grammarPool Pool to allocate grammar elements from. + /// @returns the root element of the generated grammar. The pointer should not + /// be used after the given f_grammarPool is de-allocated. + ArgParse::GrammarElement * constructGrammar(ArgParse::Grammar & f_grammarPool); +} diff --git a/src/libCli/MessageParsing.cpp b/src/libCli/MessageParsing.cpp new file mode 100644 index 0000000..391b064 --- /dev/null +++ b/src/libCli/MessageParsing.cpp @@ -0,0 +1,296 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +using namespace ArgParse; + +namespace cli +{ + +/// Parses a single field falue from a given parse tree into a protobuf message. +/// @param f_parseTree Parse tree containing the field value information. +/// @param f_message protobuf message to which the field value should be added +/// @param f_factory Factory for creation of additional messages (required for nested messages) +/// @param f_fieldDescriptor Descriptor describing the type of the field +/// @param f_isRepeated if true, field value will be added as a repeated field value. +/// (protobuf reflection api unfortunately does not provide a combined API for setting unique fields and adding to repeated fields) +/// @returns 0 if field value could be added to the message. -1 otherwise. +int parseFieldValue(ParsedElement & f_parseTree, google::protobuf::Message * f_message, google::protobuf::DynamicMessageFactory & f_factory, const google::protobuf::FieldDescriptor * f_fieldDescriptor, bool f_isRepeated = false) +{ + const google::protobuf::Reflection* reflection = f_message->GetReflection(); + std::string valueString = f_parseTree.findFirstChild("FieldValue"); + + switch(f_fieldDescriptor->cpp_type()) + { + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_FLOAT: + { + float value = std::stod(valueString); + if(f_isRepeated) + { + reflection->AddFloat(f_message, f_fieldDescriptor, value); + } + else + { + reflection->SetFloat(f_message, f_fieldDescriptor, value); + } + } + break; + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_DOUBLE: + { + double value = std::stod(valueString); + if(f_isRepeated) + { + reflection->AddDouble(f_message, f_fieldDescriptor, value); + } + else + { + reflection->SetDouble(f_message, f_fieldDescriptor, value); + } + } + break; + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_INT32: + { + long value = std::stol(valueString, 0, 0); + if(f_isRepeated) + { + reflection->AddInt32(f_message, f_fieldDescriptor, value); + } + else + { + reflection->SetInt32(f_message, f_fieldDescriptor, value); + } + } + break; + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_INT64: + { + long value = std::stol(valueString, 0, 0); + if(f_isRepeated) + { + reflection->AddInt64(f_message, f_fieldDescriptor, value); + } + else + { + reflection->SetInt64(f_message, f_fieldDescriptor, value); + } + } + break; + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_UINT32: + { + unsigned long value = std::stoul(valueString, 0, 0); + if(f_isRepeated) + { + reflection->AddUInt32(f_message, f_fieldDescriptor, value); + } + else + { + reflection->SetUInt32(f_message, f_fieldDescriptor, value); + } + } + break; + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_UINT64: + { + unsigned long value = std::stoul(valueString, 0, 0); + if(f_isRepeated) + { + reflection->AddUInt64(f_message, f_fieldDescriptor, value); + } + else + { + reflection->SetUInt64(f_message, f_fieldDescriptor, value); + } + } + break; + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_BOOL: + if( valueString == "1" || valueString == "true" || valueString == "True" ) + { + if(f_isRepeated) + { + reflection->AddBool(f_message, f_fieldDescriptor, true); + } + else + { + reflection->SetBool(f_message, f_fieldDescriptor, true); + } + } + else + { + if(f_isRepeated) + { + reflection->AddBool(f_message, f_fieldDescriptor, false); + } + else + { + reflection->SetBool(f_message, f_fieldDescriptor, false); + } + } + break; + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_ENUM: + { + const google::protobuf::EnumValueDescriptor * enumVal = f_fieldDescriptor->enum_type()->FindValueByName(valueString); + if(enumVal != nullptr) + { + if(f_isRepeated) + { + reflection->AddEnum(f_message, f_fieldDescriptor, enumVal); + } + else + { + reflection->SetEnum(f_message, f_fieldDescriptor, enumVal); + } + } + else + { + std::cerr << "Error parsing enum for field '" << f_fieldDescriptor->name() << "'" << std::endl; + return -1; + } + } + break; + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_STRING: + // we could have a string or a bytes input here + if(f_fieldDescriptor->type() == google::protobuf::FieldDescriptor::Type::TYPE_BYTES) + { + std::string resultString; + // if we have a bytes field, we parse a hex string: + if(valueString.substr(0,2) != "0x") + { + std::cerr << "Error parsing bytes field '" << f_fieldDescriptor->name() << "': Given value does not start with '0x'. Expected a hex string" << std::endl; + return -1; + } + if(valueString.size()%2 != 0) + { + std::cerr << "Error parsing bytes field '" << f_fieldDescriptor->name() << "': Given value is not a multiple of 8 bits long" << std::endl; + return -1; + } + for(size_t pos = 2; pos+1AddString(f_message, f_fieldDescriptor, resultString); + } + else + { + reflection->SetString(f_message, f_fieldDescriptor, resultString); + } + } + else + { + // otherwise we directly parse the given string: + if(f_isRepeated) + { + reflection->AddString(f_message, f_fieldDescriptor, valueString); + } + else + { + reflection->SetString(f_message, f_fieldDescriptor, valueString); + } + } + break; + case google::protobuf::FieldDescriptor::CppType::CPPTYPE_MESSAGE: + { + const google::protobuf::Descriptor * subMessageDescriptor = f_fieldDescriptor->message_type(); + std::unique_ptr subMessage = parseMessage(f_parseTree, f_factory, subMessageDescriptor); + if(subMessage != nullptr) + { + if(f_isRepeated) + { + reflection->AddAllocatedMessage(f_message, f_fieldDescriptor, subMessage.release()); + } + else + { + reflection->SetAllocatedMessage(f_message, subMessage.release(), f_fieldDescriptor); + } + } + else + { + std::cerr << "Error parsing sub-message for field '" << f_fieldDescriptor->name() << "'" << std::endl; + return -1; + } + } + break; + default: + std::cerr << "Error: Parsing Field '" << f_fieldDescriptor->name() << "'. It has the unsupported type: '" << f_fieldDescriptor->type_name() << "'" << std::endl; + return -1; + + } + + return 0; +} + +std::unique_ptr parseMessage(ParsedElement & f_parseTree, google::protobuf::DynamicMessageFactory & f_factory, const google::protobuf::Descriptor* f_messageDescriptor) +{ + std::unique_ptr message(f_factory.GetPrototype(f_messageDescriptor)->New()); + + // we iterate over all fields: + bool found = false; + ParsedElement & parsedFields = f_parseTree.findFirstSubTree("Fields", found); + if(not found) + { + std::cerr << "Error: no Fields found in parseTree for message '" << f_messageDescriptor->name() << "'" << std::endl; + return nullptr; + } + //std::cout << "Parsing message from tree: \n" << f_parseTree.getDebugString(" ") << std::endl; + int rc = 0; + for(std::shared_ptr parsedField : parsedFields.getChildren()) + { + if(parsedField->isCompletelyParsed()) + { + //std::cout << "Parsing field from tree: \n" << parsedField->getDebugString(" ") << std::endl; + const google::protobuf::FieldDescriptor * fieldDescriptor = f_messageDescriptor->FindFieldByName(parsedField->findFirstChild("FieldName")); + if(fieldDescriptor == nullptr) + { + std::cerr << "Warning: Field '" << parsedField->findFirstChild("FieldName") << "' does not exist. Ignoring." << std::endl; + continue; + } + + // now we have to parse the field value according to its type: + ParsedElement & fieldValue = parsedField->findFirstSubTree("FieldValue", found); + if(found) + { + if(fieldDescriptor->is_repeated()) + { + std::vector repeatedFieldValues; + // note: the f_doNotSearchChildsOfMatchingElements flag needs to be set to true here + // this ensures, that we can have repeated fields as part of repeated messages + fieldValue.findAllSubTrees("RepeatedValue", repeatedFieldValues, true); + for(auto repeatedValue : repeatedFieldValues) + { + rc = parseFieldValue(*repeatedValue, message.get(), f_factory, fieldDescriptor, true); + } + } + else + { + rc = parseFieldValue(fieldValue, message.get(), f_factory, fieldDescriptor); + } + } + else + { + std::cerr << "Error: No Value given for field '" << parsedField->findFirstChild("FieldName") << "'" << std::endl; + return nullptr; + } + if(rc != 0) + { + message.reset(); + break; + } + } + } + + return std::move(message); +} + +} diff --git a/src/libCli/MessageParsing.hpp b/src/libCli/MessageParsing.hpp new file mode 100644 index 0000000..5b8b733 --- /dev/null +++ b/src/libCli/MessageParsing.hpp @@ -0,0 +1,35 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +namespace cli +{ + /// Constructs a gRPC message from a given parseTree. + /// From a given parse tree, this function constructs a protobuf message and fills + /// it with all data available from the parse tree. + /// @param f_parseTree The parse tree containing al information which should be "parsed" into the protobuf message. + /// @param f_factory Required to construct messages. + /// @param f_messageDescriptor Message descriptor describing the type of the messache whoch should be constructed. + /// @returns unique_ptr pointing to a newly created message if parse succedded, + /// or an unassociated unique_ptr if parse failed. + std::unique_ptr parseMessage( + ArgParse::ParsedElement & f_parseTree, + google::protobuf::DynamicMessageFactory & f_factory, + const google::protobuf::Descriptor* f_messageDescriptor + ); +} diff --git a/src/libCli/OutputFormatting.cpp b/src/libCli/OutputFormatting.cpp new file mode 100644 index 0000000..8182b01 --- /dev/null +++ b/src/libCli/OutputFormatting.cpp @@ -0,0 +1,460 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +namespace cli +{ + + template + std::string OutputFormatter::intToHexString(T f_value) + { + std::stringstream sstream; + + sstream << getColor(ColorClass::HexValue) + << "0x" + << std::hex << std::setfill ('0') + << std::setw(2 * sizeof(T)) + << f_value + << getColor(ColorClass::Normal); + return sstream.str(); + } + + OutputFormatter::OutputFormatter() : + m_colorMap{ + {ColorClass::Normal, "\e[0m\e[39m"}, + {ColorClass::VerticalGuides, "\e[2m\e[37m"}, + {ColorClass::HorizontalGuides, "\e[2m\e[37m"}, + {ColorClass::NonRepeatedFieldName, "\e[94m"}, + {ColorClass::RepeatedFieldName, "\e[34m"}, + {ColorClass::RepeatedCount, "\e[33m"}, + {ColorClass::BoolTrue, "\e[32m"}, + {ColorClass::BoolFalse, "\e[31m"}, + {ColorClass::StringValue, "\e[33m"}, + {ColorClass::MessageTypeName, "\e[35m"}, + {ColorClass::DecimalValue, "\e[39m"}, + {ColorClass::HexValue, "\e[39m"}, + {ColorClass::EnumValue, "\e[33m"}, + } + { + + } + + void OutputFormatter::clearColorMap() + { + m_colorMap.clear(); + } + + std::string OutputFormatter::getColor(OutputFormatter::ColorClass f_colorClass) + { + auto resultIt = m_colorMap.find(f_colorClass); + if(resultIt != m_colorMap.end()) + { + return resultIt->second; + } + else + { + return ""; + } + } + + std::string OutputFormatter::colorize(OutputFormatter::ColorClass f_colorClass, const std::string & f_string) + { + return getColor(f_colorClass) + f_string + getColor(ColorClass::Normal); + } + + std::string OutputFormatter::generateHorizontalGuide(size_t f_currentSize, size_t f_targetSize) + { + std::string result = getColor(ColorClass::HorizontalGuides); + for(;f_currentSizetype()) + { + case grpc::protobuf::FieldDescriptor::Type::TYPE_MESSAGE: + { + const google::protobuf::Message & subMessage = reflection->GetRepeatedMessage(f_message, f_fieldDescriptor, f_fieldIndex); + //result += "\n" + f_currentPrefix + f_initPrefix + ":\n"; + result += colorize(ColorClass::MessageTypeName, std::string("{") + f_fieldDescriptor->message_type()->name() + "}"); + result += "\n"; + result += messageToString(subMessage,f_fieldDescriptor->message_type(), f_initPrefix, f_currentPrefix+f_initPrefix); + //result += "\n" + f_currentPrefix + f_initPrefix + ":"; + + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_SFIXED32: + case grpc::protobuf::FieldDescriptor::Type::TYPE_SINT32: + case grpc::protobuf::FieldDescriptor::Type::TYPE_INT32: + { + int32_t value = reflection->GetRepeatedInt32(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromInt(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_SFIXED64: + case grpc::protobuf::FieldDescriptor::Type::TYPE_SINT64: + case grpc::protobuf::FieldDescriptor::Type::TYPE_INT64: + { + int64_t value = reflection->GetRepeatedInt64(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromInt(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_FIXED32: + case grpc::protobuf::FieldDescriptor::Type::TYPE_UINT32: + { + uint32_t value = reflection->GetRepeatedUInt32(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromUInt(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_FIXED64: + case grpc::protobuf::FieldDescriptor::Type::TYPE_UINT64: + { + uint64_t value = reflection->GetRepeatedUInt64(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromUInt(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_FLOAT: + { + float value = reflection->GetRepeatedFloat(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromFloat(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_DOUBLE: + { + double value = reflection->GetRepeatedDouble(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromFloat(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_BOOL: + { + bool value = reflection->GetRepeatedBool(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromBool(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_STRING: + { + std::string value = reflection->GetRepeatedString(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromString(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_ENUM: + { + const google::protobuf::EnumValueDescriptor * value = reflection->GetRepeatedEnum(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromEnum(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_BYTES: + { + std::string value = reflection->GetRepeatedString(f_message, f_fieldDescriptor, f_fieldIndex); + result += stringFromBytes(value, f_modifier, f_currentPrefix + f_initPrefix); + } + break; + default: + return "repeated-" + std::string(f_fieldDescriptor->type_name()) + " is not yet supported :("; + break; + } + + return result; +} + +std::string OutputFormatter::fieldValueToString(const grpc::protobuf::Message & f_message, const google::protobuf::FieldDescriptor * f_fieldDescriptor, const std::string & f_initPrefix, const std::string & f_currentPrefix, CustomStringModifier f_modifier) +{ + const google::protobuf::Reflection * reflection = f_message.GetReflection(); + std::string result; + + // first, we need to check if this field is part of a OneOf: + const google::protobuf::OneofDescriptor * oneOfDesc = f_fieldDescriptor->containing_oneof(); + if(oneOfDesc != nullptr) + { + // yes we are part of a OneOf... + // so, we check if we are the field which is set as the OneOfType + if(reflection->GetOneofFieldDescriptor(f_message, oneOfDesc) != f_fieldDescriptor) + { + // no we are not set -> Do not continue to stringify this field, + // as it is not set. Instead we add [NOT SET] to the field string: + result += "[NOT SET]"; + // no need to decode any further... + return result; + } + } + + switch(f_fieldDescriptor->type()) + { + case grpc::protobuf::FieldDescriptor::Type::TYPE_SFIXED32: + case grpc::protobuf::FieldDescriptor::Type::TYPE_SINT32: + case grpc::protobuf::FieldDescriptor::Type::TYPE_INT32: + { + int32_t value = reflection->GetInt32(f_message, f_fieldDescriptor); + result += stringFromInt(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_SFIXED64: + case grpc::protobuf::FieldDescriptor::Type::TYPE_SINT64: + case grpc::protobuf::FieldDescriptor::Type::TYPE_INT64: + { + int64_t value = reflection->GetInt64(f_message, f_fieldDescriptor); + result += stringFromInt(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_FIXED32: + case grpc::protobuf::FieldDescriptor::Type::TYPE_UINT32: + { + uint32_t value = reflection->GetUInt32(f_message, f_fieldDescriptor); + result += stringFromUInt(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_FIXED64: + case grpc::protobuf::FieldDescriptor::Type::TYPE_UINT64: + { + uint64_t value = reflection->GetUInt64(f_message, f_fieldDescriptor); + result += stringFromUInt(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_FLOAT: + { + float value = reflection->GetFloat(f_message, f_fieldDescriptor); + result += stringFromFloat(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_DOUBLE: + { + double value = reflection->GetDouble(f_message, f_fieldDescriptor); + result += stringFromFloat(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_BOOL: + { + bool value = reflection->GetBool(f_message, f_fieldDescriptor); + result += stringFromBool(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_STRING: + { + std::string value = reflection->GetString(f_message, f_fieldDescriptor); + result += stringFromString(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_ENUM: + { + const google::protobuf::EnumValueDescriptor * value = reflection->GetEnum(f_message, f_fieldDescriptor); + result += stringFromEnum(value, f_modifier); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_BYTES: + { + std::string value = reflection->GetString(f_message, f_fieldDescriptor); + result += stringFromBytes(value, f_modifier, f_currentPrefix + f_initPrefix); + } + break; + case grpc::protobuf::FieldDescriptor::Type::TYPE_MESSAGE: + { + const google::protobuf::Message & subMessage = reflection->GetMessage(f_message, f_fieldDescriptor); + //result += ":\n"; + result += colorize(ColorClass::MessageTypeName, std::string("{") + f_fieldDescriptor->message_type()->name() + "}"); + result += "\n"; + result += messageToString(subMessage,f_fieldDescriptor->message_type(), f_initPrefix, f_currentPrefix+f_initPrefix); + //result += "\n" + f_currentPrefix + f_initPrefix + ":"; + } + break; + default: + return std::string(f_fieldDescriptor->type_name()) + " is not yet supported :("; + break; + } + + return result; +} + +std::string OutputFormatter::fieldToString(const grpc::protobuf::Message & f_message, const google::protobuf::FieldDescriptor * f_fieldDescriptor, const std::string & f_initPrefix, const std::string & f_currentPrefix, size_t maxFieldNameSize) +{ + std::string result; + const google::protobuf::Reflection * reflection = f_message.GetReflection(); + + if(f_fieldDescriptor->is_repeated()) + { + int numberOfRepetitions = reflection->FieldSize(f_message, f_fieldDescriptor); + if(numberOfRepetitions == 0) + { + // TODO: remove duplicate code + result += colorize(ColorClass::VerticalGuides, f_currentPrefix); + std::string repName; + repName += colorize(ColorClass::RepeatedFieldName, f_fieldDescriptor->name()); + repName += colorize(ColorClass::RepeatedCount, "[0/0]"); + result += repName; + size_t nameSize = repName.size(); + result += generateHorizontalGuide(nameSize, maxFieldNameSize); + result += " = " + colorize(ColorClass::MessageTypeName, "{}"); + } + for(int i = 0; i < numberOfRepetitions; i++) + { + if(i!=0) + { + result += "\n"; + } + result += colorize(ColorClass::VerticalGuides, f_currentPrefix); + std::string repName; + repName += getColor(ColorClass::RepeatedFieldName) + f_fieldDescriptor->name() + getColor(ColorClass::Normal); + repName += getColor(ColorClass::RepeatedCount) + "[" + std::to_string(i+1) + "/" + std::to_string(numberOfRepetitions) + "]" + getColor(ColorClass::Normal); + result += repName; + size_t nameSize = repName.size(); + result += generateHorizontalGuide(nameSize, maxFieldNameSize); + result += " = "; + result += repeatedFieldValueToString(f_message, f_fieldDescriptor, f_initPrefix, f_currentPrefix, i); + } + + } + else + { + result += colorize(ColorClass::VerticalGuides, f_currentPrefix); + result += colorize(ColorClass::NonRepeatedFieldName, f_fieldDescriptor->name()); + size_t nameSize = f_fieldDescriptor->name().size(); + result += generateHorizontalGuide(nameSize, maxFieldNameSize); + result += " = "; + result += fieldValueToString(f_message, f_fieldDescriptor, f_initPrefix, f_currentPrefix); + } + + return result; +} + +std::string OutputFormatter::messageToString(const grpc::protobuf::Message & f_message, const grpc::protobuf::Descriptor* f_messageDescriptor, const std::string & f_initPrefix, const std::string & f_currentPrefix) +{ + std::string result; + // first determine field name length maximum (for aligned formatting) + size_t maxFieldNameLength = 0; + for(int i = 0; i< f_messageDescriptor->field_count(); i++) + { + const google::protobuf::FieldDescriptor * fieldDesc = f_messageDescriptor->field(i); + size_t thisFieldNameLength = fieldDesc->name().size(); + + if(fieldDesc->is_repeated()) + { + const google::protobuf::Reflection * reflection = f_message.GetReflection(); + int numberOfRepetitions = reflection->FieldSize(f_message, fieldDesc); + std::string simulatedMaxArr = "[" + std::to_string(numberOfRepetitions) + "/" + std::to_string(numberOfRepetitions) + "]"; + thisFieldNameLength += simulatedMaxArr.size(); + + + } + if(thisFieldNameLength > maxFieldNameLength) + { + maxFieldNameLength = thisFieldNameLength; + } + } + + for(int i = 0; i< f_messageDescriptor->field_count(); i++) + { + const google::protobuf::FieldDescriptor * fieldDesc = f_messageDescriptor->field(i); + if(i!=0) + { + result += "\n"; + } + result += fieldToString(f_message, fieldDesc, f_initPrefix, f_currentPrefix, maxFieldNameLength); + } + return result; +} + +std::string OutputFormatter::stringFromBytes(const std::string & f_value, const CustomStringModifier & f_modifier, const std::string & f_prefix) +{ + std::string result; + // a simple hexdump: + const std::string & prefix = f_prefix; + result += "hex[" + std::to_string(f_value.size()) + "]"; + + std::string stringRepresentation = ""; + size_t maxAddrTextSize = std::to_string(f_value.size()-1).size(); + for(size_t i = 0; i 8) + { + result += "\n" + prefix; + std::stringstream streamAddr; + streamAddr << std::setfill (' ') << std::setw(maxAddrTextSize); + // TODO: should place address as hex also... + streamAddr << i; + result += streamAddr.str(); + result += ": "; + } + else + { + result += " = "; + } + } + else if(i%4 == 0) + { + result += " "; + } + else + { + result += " "; + } + } + + // now do the actual hexdump: + // cast to uint16_t necessary, otherwise stream interprets the value as a char. + uint16_t byteVal = f_value[i] & 0xff; + std::stringstream stream; + stream << std::setfill ('0') << std::setw(2) << std::hex; + stream << byteVal; + result += stream.str(); + + // create string representation: + if( (f_value[i] >= 32) and (f_value[i] <= 126) ) + { + // string representable character range: + stringRepresentation += f_value[i]; + } + else + { + // special characters + stringRepresentation += "."; + } + + i++; + + // Place string representation, when appropriate: + if( (i%8 == 0) or (i>=f_value.size()) ) + { + size_t padding = (8 - i%8)%8; + if(padding>3) + { + padding *=3; + padding += 1; + } + else + { + padding *=3; + } + result += std::string(padding, ' '); + result += " |" + stringRepresentation + "|"; + stringRepresentation = ""; + } + } + return result; +} +} diff --git a/src/libCli/OutputFormatting.hpp b/src/libCli/OutputFormatting.hpp new file mode 100644 index 0000000..50d08e7 --- /dev/null +++ b/src/libCli/OutputFormatting.hpp @@ -0,0 +1,150 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include + +#include +#include + +namespace cli +{ + /// Class with methods to format a protobuf message into human readable strings. + class OutputFormatter + { + public: + /// Initializes the OutputFormatter with default settings. + /// Includes initialization of the default color theme. + OutputFormatter(); + + /// Enum containing all possible elements for colorization. + enum class ColorClass + { + Normal, // Used for all text not covered by a color class + NonRepeatedFieldName, // Field name of non-repeated fields + RepeatedFieldName, // Field name of repeated fields + VerticalGuides, // The indentation string + HorizontalGuides, // The filling string (dots) from fieldname to value + RepeatedCount, // Counter (both: current and total) of repeated fields + BoolTrue, // true value of bools + BoolFalse, // false value of bools + StringValue, // string values + MessageTypeName, // type name of messages + DecimalValue, // devimal values of numbers + HexValue, // hex value of numbers + EnumValue // enum values + }; + + /// Possible modifiers, which may be used to control how the formatter converts certain types into string. + enum class CustomStringModifier + { + None, // does not change behavior + Raw // Prints integers in hex and without colorization. TODO: better name + }; + + /// Formats a protobuf message into a human readable string. + /// @param f_message the protobuf message to be formatted + /// @param f_messageDescriptor descriptor describing the message type + /// @param f_initPrefix Each new line in formatted output will be prefixed with 0 or more instances of this string corresponding to the indentation level. + /// @param f_currentPrefix Each new line in formatted output will be prefixed with this sting. May be used for an initial prefix/indentation of output text. + std::string messageToString( + const grpc::protobuf::Message & f_message, + const grpc::protobuf::Descriptor* f_messageDescriptor, + const std::string & f_initPrefix = " ", + const std::string & f_currentPrefix = "" + ); + + /// Clears the color map. + /// Causes all output to be generated with default font (no terminal control characters). + void clearColorMap(); + + // TODO: provide the option to provide custom color map + // e.g. via config file or cli args + + /// Formats a field value as string. + /// NOTE: required for custom output format + std::string fieldValueToString(const grpc::protobuf::Message & f_message, const google::protobuf::FieldDescriptor * f_fieldDescriptor, const std::string & f_initPrefix, const std::string & f_currentPrefix, CustomStringModifier f_modifier = CustomStringModifier::None); + + /// Formats a repeated field value as string. + /// NOTE: required for custom output format + std::string repeatedFieldValueToString(const grpc::protobuf::Message & f_message, const google::protobuf::FieldDescriptor * f_fieldDescriptor, const std::string & f_initPrefix, const std::string & f_currentPrefix, int f_fieldIndex, CustomStringModifier f_modifier = CustomStringModifier::None); + + private: + std::map m_colorMap; + std::string generateHorizontalGuide(size_t f_currentSize, size_t f_targetSize); + std::string getColor(ColorClass f_colorClass); + std::string colorize(ColorClass f_colorClass, const std::string & f_string); + std::string fieldToString(const grpc::protobuf::Message & f_message, const google::protobuf::FieldDescriptor * f_fieldDescriptor, const std::string & f_initPrefix, const std::string & f_currentPrefix, size_t maxFieldNameSize); + template std::string intToHexString(T f_value); + + // string formatting methods for various types: + template + std::string stringFromInt(T f_value, const CustomStringModifier & f_modifier) + { + std::string result; + if(f_modifier == CustomStringModifier::Raw) + { + result += intToHexString(f_value); + } + else + { + result += colorize(ColorClass::DecimalValue, std::to_string(f_value)); + } + return result; + } + + template + std::string stringFromUInt(T f_value, const CustomStringModifier & f_modifier) + { + std::string result; + if(f_modifier == CustomStringModifier::Raw) + { + result += intToHexString(f_value); + } + else + { + std::stringstream stream; + stream << colorize(ColorClass::DecimalValue, std::to_string(f_value)) + << " (" << intToHexString(f_value) << ")" + << getColor(ColorClass::Normal); + result += stream.str(); + } + return result; + } + + template + std::string stringFromFloat(T f_value, const CustomStringModifier & f_modifier) + { + return colorize(ColorClass::DecimalValue, std::to_string(f_value)); + } + + std::string stringFromBool(bool f_value, const CustomStringModifier & f_modifier) + { + return (f_value ? colorize(ColorClass::BoolTrue,"true") : colorize(ColorClass::BoolFalse,"false")); + } + + std::string stringFromString(const std::string & f_value, const CustomStringModifier & f_modifier) + { + return colorize(ColorClass::StringValue, "\"" + f_value + "\""); + } + + std::string stringFromEnum(const google::protobuf::EnumValueDescriptor * f_value, const CustomStringModifier & f_modifier) + { + return colorize(ColorClass::EnumValue, f_value->name()); + } + + std::string stringFromBytes(const std::string & f_value, const CustomStringModifier & f_modifier, const std::string & f_prefix); + + }; +} diff --git a/tests/AlternationTest.cpp b/tests/AlternationTest.cpp new file mode 100644 index 0000000..f082384 --- /dev/null +++ b/tests/AlternationTest.cpp @@ -0,0 +1,312 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +using namespace ArgParse; + +// ----------------------------------------------------------------------------- +// Alternation +// ----------------------------------------------------------------------------- + +TEST(AlternationTest, NoChildEmptyString) { + EXPECT_EQ(true, true); + + Alternation myAlternation; + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myAlternation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); +} + +TEST(AlternationTest, NoChildNonEmptyString) { + Alternation myAlternation; + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myAlternation.parse("test", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); +} + +TEST(AlternationTest, OneChildEmptyString) { + FixedString child1("child1"); + Alternation myAlternation; + myAlternation.addChild(&child1); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myAlternation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myAlternation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); +} + +TEST(AlternationTest, TwoChildsSimilarStart) { + FixedString child1("child1"); + FixedString child2("child12345"); + Alternation myAlternation; + myAlternation.addChild(&child1); + myAlternation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myAlternation.parse("child12345", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(strlen("child12345"), rc.lenParsed); + EXPECT_EQ(strlen("child12345"), rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); +} + +TEST(AlternationTest, TwoChildEmptyString) { + FixedString child1("child1"); + FixedString child2("child2"); + Alternation myAlternation; + myAlternation.addChild(&child1); + myAlternation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myAlternation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("child1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myAlternation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("child2", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&myAlternation, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); +} + +TEST(AlternationTest, TwoChildCorrectStartString) { + FixedString child1("child1"); + FixedString child2("child2"); + Alternation myAlternation; + myAlternation.addChild(&child1); + myAlternation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myAlternation.parse("child", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(strlen("child"), rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("child1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myAlternation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("child2", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&myAlternation, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); +} + +TEST(AlternationTest, TwoChildUniquePartialMatch) { + FixedString child1("child1asd"); + FixedString child2("child2fgh"); + Alternation myAlternation; + myAlternation.addChild(&child1); + myAlternation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myAlternation.parse("child2", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(strlen("child2"), rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child2fgh", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myAlternation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); + EXPECT_EQ(&child2, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); +} + +TEST(AlternationTest, TwoChildWrongString) { + FixedString child1("child1"); + FixedString child2("child2"); + Alternation myAlternation; + myAlternation.addChild(&child1); + myAlternation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myAlternation.parse("asd", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::unexpectedText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); +} + +TEST(AlternationTest, TwoChildCorrectStringForBoth) { + FixedString child1("child1"); + FixedString child2("child123"); + Alternation myAlternation; + myAlternation.addChild(&child1); + myAlternation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myAlternation.parse("child1", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(6, rc.lenParsed); + EXPECT_EQ(6, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + + // parsedElement + // hmmm what to expect here: one or two? i.e. is the matched one also a candidate (maybe relevant for further completion...) + // -> hm maybe not: maybe we should in general continue parsing + // -> hmmm we actually do this already!!! why is it not working? + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myAlternation, parsedElement.getGrammarElement()); +} + +// Not yet supported +//TEST(AlternationTest, OptionalChild) { +// FixedString child1("child1"); +// Optional child2; +// FixedString childOpt("child2"); +// child2.addChild(&childOpt); +// +// Alternation myAlternation; +// myAlternation.addChild(&child1); +// myAlternation.addChild(&child2); +// +// ParsedElement parent; +// ParsedElement parsedElement(&parent); +// +// ParseRc rc = myAlternation.parse("", parsedElement); +// +// // rc: +// EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); +// EXPECT_EQ(0, rc.lenParsedSuccessfully); +// +// // candidates: +// ASSERT_EQ(2, rc.candidates.size()); +// EXPECT_EQ("child1", rc.candidates[0]->getMatchedString()); +// EXPECT_EQ(&myAlternation, rc.candidates[0]->getGrammarElement()); +// EXPECT_EQ(&parent, rc.candidates[0]->getParent()); +// +// EXPECT_EQ("child2", rc.candidates[1]->getMatchedString()); +// EXPECT_EQ(&myAlternation, rc.candidates[1]->getGrammarElement()); +// EXPECT_EQ(&parent, rc.candidates[1]->getParent()); +// +// // parsedElement +// ASSERT_EQ(0, parsedElement.getChildren().size()); +// EXPECT_EQ(&parent, parsedElement.getParent()); +// EXPECT_EQ(false, parsedElement.isStopped()); +// EXPECT_EQ(nullptr, parsedElement.getGrammarElement()); +//} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..2225664 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8) +project (gWhisper) + +include_directories("${PROJECT_BINARY_DIR}") + +set(TARGET_NAME "gwhisper_tests") +set(TARGET_SRC + FixedStringTest.cpp + ConcatenationTest.cpp + AlternationTest.cpp + RepetitionTest.cpp + GrammarComboTests.cpp + testmain.cpp + ) + +add_executable(${TARGET_NAME} ${TARGET_SRC}) + +target_link_libraries (${TARGET_NAME} + reflection + gtest + ) +if(BUILD_CONFIG_USE_BOOST_REGEX) + target_link_libraries (${TARGET_NAME} + boost_regex + ) +endif() + +add_test(NAME UnitTests COMMAND ${TARGET_NAME}) diff --git a/tests/ConcatenationTest.cpp b/tests/ConcatenationTest.cpp new file mode 100644 index 0000000..9af2321 --- /dev/null +++ b/tests/ConcatenationTest.cpp @@ -0,0 +1,350 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +using namespace ArgParse; + +// ----------------------------------------------------------------------------- +// Concatenation +// ----------------------------------------------------------------------------- + +TEST(ConcatenationTest, NoChildEmptyString) { + EXPECT_EQ(true, true); + + Concatenation myConcatenation; + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(ConcatenationTest, NoChildNonEmptyString) { + Concatenation myConcatenation; + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("test", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(ConcatenationTest, OneChildEmptyString) { + FixedString child1("child1"); + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + ASSERT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + ASSERT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(ConcatenationTest, TwoChildsCompleteMatch) { + FixedString child1("child1"); + FixedString child2("child2"); + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("child1child2asd", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(strlen("child1child2"), rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(2, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(ConcatenationTest, TwoChildsSecondIsPartialMatch) { + FixedString child1("child1"); + FixedString child2("child2"); + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("child1c", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(strlen("child1c"), rc.lenParsed); + EXPECT_EQ(strlen("child1"), rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child1child2", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(2, parsedElement.getChildren().size()); + EXPECT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("child1", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&child2, parsedElement.getChildren()[1]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[1]->getMatchedString()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(ConcatenationTest, TwoChildEmptyString) { + FixedString child1("child1"); + FixedString child2("child2"); + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child1child2", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(ConcatenationTest, TwoChildCorrectStartString) { + FixedString child1("child1"); + FixedString child2("child2"); + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("child", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child1child2", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(ConcatenationTest, TwoChildWrongString) { + FixedString child1("child1"); + FixedString child2("child2"); + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&child2); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("asd", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::unexpectedText, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(ConcatenationTest, SingleAlternationFork) { + FixedString child1("child1"); + Alternation alt; + FixedString altChild1("ac1"); + FixedString altChild2("ac2"); + alt.addChild(&altChild1); + alt.addChild(&altChild2); + + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&alt); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("child1ac1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("child1ac2", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(ConcatenationTest, SingleAlternationNoFork) { + FixedString child1("child1"); + Alternation alt; + FixedString altChild1("ac1"); + FixedString altChild2("ac2"); + alt.addChild(&altChild1); + alt.addChild(&altChild2); + + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&alt); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("", parsedElement, 0); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +// Not yet supported +//TEST(ConcatenationTest, OptionalChild) { +// FixedString child1("child1"); +// Optional child2; +// FixedString childOpt("child2"); +// child2.addChild(&childOpt); +// +// Concatenation myConcatenation; +// myConcatenation.addChild(&child1); +// myConcatenation.addChild(&child2); +// +// ParsedElement parent; +// ParsedElement parsedElement(&parent); +// +// ParseRc rc = myConcatenation.parse("", parsedElement); +// +// // rc: +// EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); +// EXPECT_EQ(0, rc.lenParsedSuccessfully); +// +// // candidates: +// ASSERT_EQ(2, rc.candidates.size()); +// EXPECT_EQ("child1", rc.candidates[0]->getMatchedString()); +// EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); +// EXPECT_EQ(&parent, rc.candidates[0]->getParent()); +// +// EXPECT_EQ("child2", rc.candidates[1]->getMatchedString()); +// EXPECT_EQ(&myConcatenation, rc.candidates[1]->getGrammarElement()); +// EXPECT_EQ(&parent, rc.candidates[1]->getParent()); +// +// // parsedElement +// ASSERT_EQ(0, parsedElement.getChildren().size()); +// EXPECT_EQ(&parent, parsedElement.getParent()); +// EXPECT_EQ(false, parsedElement.isStopped()); +// EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +//} diff --git a/tests/FixedStringTest.cpp b/tests/FixedStringTest.cpp new file mode 100644 index 0000000..272c07c --- /dev/null +++ b/tests/FixedStringTest.cpp @@ -0,0 +1,102 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +using namespace ArgParse; + +// ----------------------------------------------------------------------------- +// FixedString +// ----------------------------------------------------------------------------- + +TEST(FixedStringTest, EmptyFixedStringEmptyString) { + FixedString myFixedString(""); + ParsedElement parsedElement; + + ParseRc rc = myFixedString.parse("", parsedElement); + + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.candidates.size()); + EXPECT_EQ(0, rc.lenParsedSuccessfully); +} + +TEST(FixedStringTest, EmptyFixedStringNonEmptyString) { + FixedString myFixedString(""); + ParsedElement parsedElement; + + ParseRc rc = myFixedString.parse("test", parsedElement); + + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.candidates.size()); + EXPECT_EQ(0, rc.lenParsedSuccessfully); +} + +TEST(FixedStringTest, EmptyString) { + FixedString myFixedString("asd"); + ParsedElement parsedElement; + + ParseRc rc = myFixedString.parse("", parsedElement); + + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(1, rc.candidates.size()); + EXPECT_EQ("asd", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + EXPECT_EQ(&myFixedString, rc.candidates[0]->getGrammarElement()); +} + +TEST(FixedStringTest, MatchingString) { + FixedString myFixedString("asd"); + ParsedElement parsedElement; + + ParseRc rc = myFixedString.parse("asd", parsedElement); + + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(3, rc.lenParsedSuccessfully); + EXPECT_EQ(0, rc.candidates.size()); +} + +TEST(FixedStringTest, MatchingStringTooMuch) { + FixedString myFixedString("asd"); + ParsedElement parsedElement; + + ParseRc rc = myFixedString.parse("asdfgh", parsedElement); + + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(3, rc.lenParsedSuccessfully); + EXPECT_EQ(0, rc.candidates.size()); +} + +TEST(FixedStringTest, HalfMatchingString) { + FixedString myFixedString("asd"); + ParsedElement parsedElement; + + ParseRc rc = myFixedString.parse("a", parsedElement); + + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(1, rc.candidates.size()); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + EXPECT_EQ("asd", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myFixedString, rc.candidates[0]->getGrammarElement()); +} + +TEST(FixedStringTest, NonMatchingString) { + FixedString myFixedString("asd"); + ParsedElement parsedElement; + + ParseRc rc = myFixedString.parse("b", parsedElement); + + EXPECT_EQ(ParseRc::ErrorType::unexpectedText, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + EXPECT_EQ(0, rc.candidates.size()); +} diff --git a/tests/GrammarComboTests.cpp b/tests/GrammarComboTests.cpp new file mode 100644 index 0000000..48f35c8 --- /dev/null +++ b/tests/GrammarComboTests.cpp @@ -0,0 +1,1013 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +using namespace ArgParse; + +// ---------------------------------------------------------------------------- +// CA Combos +// ---------------------------------------------------------------------------- + +TEST(CA_ComboTest, CAC_combo) { + // c1 + // a1 + // f1 + // f2 + // c2 + // f3 + // f4 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + + Concatenation c1; + Concatenation c2; + + a1.addChild(&f1); + a1.addChild(&f2); + + c1.addChild(&a1); + c1.addChild(&c2); + + c2.addChild(&f3); + c2.addChild(&f4); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("f1f3f4", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("f2f3f4", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&a1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(0, parsedElement.getChildren()[0]->getChildren().size()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, CAC_combo_uniquePartialMatch) { + // c1 + // a1 + // f1 + // f2 + // c2 + // f3 + // f4 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + + Concatenation c1; + Concatenation c2; + + a1.addChild(&f1); + a1.addChild(&f2); + + c1.addChild(&a1); + c1.addChild(&c2); + + c2.addChild(&f3); + c2.addChild(&f4); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("f2", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType) << rc.toString(); + EXPECT_EQ(2, rc.lenParsed); + EXPECT_EQ(2, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("f2f3f4", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + // It is essential to have the first child here (otherwise grammar injection would not have access to parially parsed tree) + ASSERT_EQ(2, parsedElement.getChildren().size()); + EXPECT_EQ("f2", parsedElement.getMatchedString()); + + EXPECT_EQ(&a1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("f2", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(1, parsedElement.getChildren()[0]->getChildren().size()); + + EXPECT_EQ(&c2, parsedElement.getChildren()[1]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[1]->getMatchedString()); + EXPECT_EQ(1, parsedElement.getChildren()[1]->getChildren().size()); + EXPECT_EQ(&f3, parsedElement.getChildren()[1]->getChildren()[0]->getGrammarElement()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, CA_combo_partialMatch) { + // c1 + // a1 + // f1 + // f2 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + Concatenation c1; + + a1.addChild(&f1); + a1.addChild(&f2); + + c1.addChild(&a1); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("f", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(1, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("f1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("f2", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&a1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + + EXPECT_EQ(0, parsedElement.getChildren()[0]->getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, CA_combo_partialMatchFollowUpAlternation) { + // c1 + // a1 + // f1 + // f2 + // a2 + // f3 + // f4 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + Concatenation c1; + + a1.addChild(&f1); + a1.addChild(&f2); + + a2.addChild(&f3); + a2.addChild(&f4); + + c1.addChild(&a1); + c1.addChild(&a2); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("f", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(1, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("f1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("f2", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&a1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + + EXPECT_EQ(0, parsedElement.getChildren()[0]->getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, CCX_combo_noOverParse) { + // c1 + // c2 + // f1 + // x1 + // f3 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + RegEx x1("/asdfsadfsadf/"); + Concatenation c1; + Concatenation c2; + + c1.addChild(&c2); + c1.addChild(&f3); + + c2.addChild(&f1); + c2.addChild(&x1); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("f1", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(2, rc.lenParsed); + EXPECT_EQ(2, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&c2, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("f1", parsedElement.getChildren()[0]->getMatchedString()); + + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, CCX_combo_noOverParseUnexpectedText) { + // c1 + // c2 + // f1 + // x1 + // f3 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + RegEx x1("/asdfsadfsadf/"); + Concatenation c1; + Concatenation c2; + + c1.addChild(&c2); + c1.addChild(&f3); + + c2.addChild(&f1); + c2.addChild(&x1); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("f1h", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::unexpectedText, rc.errorType); + EXPECT_EQ(3, rc.lenParsed); + EXPECT_EQ(2, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, CCX_combo_noOverParsePartial) { + // c1 + // c2 + // f1 + // x1 + // f3 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + RegEx x1("/asdfsadfsadf/"); + Concatenation c1; + Concatenation c2; + + c1.addChild(&c2); + c1.addChild(&f3); + + c2.addChild(&f1); + c2.addChild(&x1); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("f", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(1, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("f1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, CA_combo) { + // c1 + // a1 + // f1 + // f2 + // f3 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + Concatenation c1; + + a1.addChild(&f1); + a1.addChild(&f2); + + c1.addChild(&a1); + c1.addChild(&f3); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("f1f3", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("f2f3", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&a1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + + EXPECT_EQ(0, parsedElement.getChildren()[0]->getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, ACACA_combo_NoFork) { + // a1 + // c1 + // f1 + // a2 + // f2 + // f3 + // c2 + // f4 + // a3 + // f5 + // f6 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + FixedString f6("f6"); + Alternation a1; + Alternation a2; + Alternation a3; + Concatenation c1; + Concatenation c2; + + a1.addChild(&c1); + a1.addChild(&c2); + + c1.addChild(&f1); + c1.addChild(&a2); + + c2.addChild(&f4); + c2.addChild(&a3); + + a2.addChild(&f2); + a2.addChild(&f3); + + a3.addChild(&f5); + a3.addChild(&f6); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = a1.parse("f", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(1, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("f1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&a1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("f4", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&a1, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&a1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, ACA_combo_NoFork) { + // a1 + // c1 + // f1 + // a2 + // f2 + // f3 + // f4 + // f5 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + Concatenation c1; + + a1.addChild(&c1); + a1.addChild(&f5); + + c1.addChild(&f1); + c1.addChild(&a2); + c1.addChild(&f4); + + a2.addChild(&f2); + a2.addChild(&f3); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = a1.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("f1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&a1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("f5", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&a1, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(0, parsedElement.getChildren().size()); // This might change to 1, if we enforce the "if it is unique, we add it" paradigm to the limit. I.e. if user has stopped inputting, we just continue parsing + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&a1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, ACA_combo_PartialMatchFork) { + // a1 + // c1 + // f1 + // a2 + // f2 + // f3 + // f4 + // f5 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Alternation a1; + Alternation a2; + Concatenation c1; + + a1.addChild(&c1); + a1.addChild(&f5); + + c1.addChild(&f1); + c1.addChild(&a2); + c1.addChild(&f4); + + a2.addChild(&f2); + a2.addChild(&f3); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = a1.parse("f1f", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(3, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("f1f2f4", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&a1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("f1f3f4", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&a1, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&a1, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, DoubleConcatWithAlternationFork) { + FixedString child1("f1"); + FixedString child2("f2"); + FixedString child3("f3"); + FixedString child4("f4"); + + Alternation alt; + alt.addChild(&child3); + alt.addChild(&child4); + + Concatenation c2; + c2.addChild(&child2); + c2.addChild(&alt); + + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&c2); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("f1f2f3", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("f1f2f4", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, RecursiveAlternationFork) { + FixedString child1("child1"); + + Alternation alt; + FixedString altChild1("ac1"); + FixedString altChild2("ac2"); + alt.addChild(&altChild1); + alt.addChild(&altChild2); + + Alternation altParent; + FixedString altChild3("ac3"); + altParent.addChild(&alt); + altParent.addChild(&altChild3); + + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&altParent); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(3, rc.candidates.size()); + EXPECT_EQ("child1ac1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("child1ac2", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + EXPECT_EQ("child1ac3", rc.candidates[2]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[2]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[2]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +TEST(CA_ComboTest, TwoAlternationNoFork) { + FixedString child1("child1"); + + Alternation alt1; + FixedString altChild1("ac1"); + FixedString altChild2("ac2"); + alt1.addChild(&altChild1); + alt1.addChild(&altChild2); + + Alternation alt2; + FixedString altChild3("ac3"); + FixedString altChild4("ac4"); + alt2.addChild(&altChild3); + alt2.addChild(&altChild4); + + Concatenation myConcatenation; + myConcatenation.addChild(&child1); + myConcatenation.addChild(&alt1); + myConcatenation.addChild(&alt2); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = myConcatenation.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(2, rc.candidates.size()); + EXPECT_EQ("child1ac1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + EXPECT_EQ("child1ac2", rc.candidates[1]->getMatchedString()); + EXPECT_EQ(&myConcatenation, rc.candidates[1]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[1]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&child1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&myConcatenation, parsedElement.getGrammarElement()); +} + +// ---------------------------------------------------------------------------- +// CR Combos +// ---------------------------------------------------------------------------- + +TEST(CR_ComboTest, CR_combo_Empty) { + // c1 + // r1 + // f1 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Repetition r1; + Concatenation c1; + + r1.addChild(&f1); + + c1.addChild(&r1); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("f1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&r1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(1, parsedElement.getChildren()[0]->getChildren().size()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CR_ComboTest, CR_combo_PartialMatchSandwitchVeryComplex) { + // c1 + // f1 + // r1 + // c2 + // f2 + // f4 + // f3 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3(":"); + FixedString f4("8"); + Repetition r1; + Concatenation c1; + Concatenation c2; + + r1.addChild(&c2); + + c1.addChild(&f1); + c1.addChild(&r1); + c1.addChild(&f3); + + c2.addChild(&f2); + c2.addChild(&f4); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("f1f2", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(4, rc.lenParsed); + EXPECT_EQ(4, rc.lenParsedSuccessfully); + + //EXPECT_EQ("f1f2faa2", rc.candidates[0]->getMatchedString()); + //EXPECT_EQ("f1f2faa2", rc.candidates[1]->getMatchedString()); + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("f1f28", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(2, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&f1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("f1", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&r1, parsedElement.getChildren()[1]->getGrammarElement()); + EXPECT_EQ("f2", parsedElement.getChildren()[1]->getMatchedString()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CR_ComboTest, CR_combo_PartialMatchSandwitch) { + // c1 + // f1 + // r1 + // f2 + // f3 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3(":"); + Repetition r1; + Concatenation c1; + + r1.addChild(&f2); + + c1.addChild(&f1); + c1.addChild(&r1); + c1.addChild(&f3); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("f1f2f", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(5, rc.lenParsed); + EXPECT_EQ(4, rc.lenParsedSuccessfully); + + //EXPECT_EQ("f1f2faa2", rc.candidates[0]->getMatchedString()); + //EXPECT_EQ("f1f2faa2", rc.candidates[1]->getMatchedString()); + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("f1f2f2", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(2, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&f1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("f1", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&r1, parsedElement.getChildren()[1]->getGrammarElement()); + EXPECT_EQ("f2", parsedElement.getChildren()[1]->getMatchedString()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CR_ComboTest, CR_combo_PartialMatch) { + // c1 + // r1 + // f1 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Repetition r1; + Concatenation c1; + + r1.addChild(&f1); + + c1.addChild(&r1); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = c1.parse("f", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(1, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("f1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&c1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&r1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(1, parsedElement.getChildren()[0]->getChildren().size()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&c1, parsedElement.getGrammarElement()); +} + +TEST(CR_ComboTest, CR_combo_RC_partialMatch) { + // r1 + // c1 + // f1 + + FixedString f1("f1"); + FixedString f2("f2"); + FixedString f3("f3"); + FixedString f4("f4"); + FixedString f5("f5"); + Repetition r1; + Concatenation c1; + + r1.addChild(&c1); + + c1.addChild(&f1); + + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = r1.parse("f", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(1, rc.lenParsed); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("f1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&r1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(&c1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(1, parsedElement.getChildren()[0]->getChildren().size()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&r1, parsedElement.getGrammarElement()); +} + diff --git a/tests/RepetitionTest.cpp b/tests/RepetitionTest.cpp new file mode 100644 index 0000000..11c137a --- /dev/null +++ b/tests/RepetitionTest.cpp @@ -0,0 +1,185 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +using namespace ArgParse; + +TEST(RepetitionTest, NoChildEmptyString) { + Repetition r1; + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = r1.parse("", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + EXPECT_EQ("", parsedElement.getMatchedString()); + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&r1, parsedElement.getGrammarElement()); +} + +TEST(RepetitionTest, NoChildFilledString) { + Repetition r1; + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = r1.parse("asd", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + EXPECT_EQ("", parsedElement.getMatchedString()); + ASSERT_EQ(0, parsedElement.getChildren().size()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&r1, parsedElement.getGrammarElement()); +} + + +TEST(RepetitionTest, ExactMatch) { + FixedString c1("child1"); + Repetition r1; + r1.addChild(&c1); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = r1.parse("child1", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(strlen("child1"), rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child1child1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&r1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + EXPECT_EQ("child1", parsedElement.getMatchedString()); + ASSERT_EQ(2, parsedElement.getChildren().size()); + EXPECT_EQ(&c1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("child1", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&c1, parsedElement.getChildren()[1]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[1]->getMatchedString()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&r1, parsedElement.getGrammarElement()); +} + + +TEST(RepetitionTest, MultipleMatch) { + FixedString c1("child1"); + Repetition r1; + r1.addChild(&c1); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = r1.parse("child1child1child1", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(strlen("child1child1child1"), rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child1child1child1child1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&r1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + EXPECT_EQ("child1child1child1", parsedElement.getMatchedString()); + ASSERT_EQ(4, parsedElement.getChildren().size()); + EXPECT_EQ(&c1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("child1", parsedElement.getChildren()[0]->getMatchedString()); + EXPECT_EQ(&c1, parsedElement.getChildren()[3]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[3]->getMatchedString()); + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&r1, parsedElement.getGrammarElement()); +} + +TEST(RepetitionTest, NoMatch) { + FixedString c1("child1"); + Repetition r1; + r1.addChild(&c1); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = r1.parse("asd", parsedElement); + + // rc: + EXPECT_EQ(ParseRc::ErrorType::success, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + // We do not expect any candidates, as we already have non matching text + ASSERT_EQ(0, rc.candidates.size()); + + // parsedElement + EXPECT_EQ("", parsedElement.getMatchedString()); + // also do not expect any parsed elements, as we could not uniquely identify + // any (partially) matching childs + ASSERT_EQ(0, parsedElement.getChildren().size()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&r1, parsedElement.getGrammarElement()); +} + +TEST(RepetitionTest, PartialMatch) { + FixedString c1("child1"); + Repetition r1; + r1.addChild(&c1); + ParsedElement parent; + ParsedElement parsedElement(&parent); + + ParseRc rc = r1.parse("ch", parsedElement); + + // rc: + // FIXME: what do want to have as RC here ??? we are not really expecting any more text, as a match of 0 would be fine... + EXPECT_EQ(ParseRc::ErrorType::missingText, rc.errorType); + EXPECT_EQ(0, rc.lenParsedSuccessfully); + + // candidates: + ASSERT_EQ(1, rc.candidates.size()); + EXPECT_EQ("child1", rc.candidates[0]->getMatchedString()); + EXPECT_EQ(&r1, rc.candidates[0]->getGrammarElement()); + EXPECT_EQ(&parent, rc.candidates[0]->getParent()); + + // parsedElement + EXPECT_EQ("", parsedElement.getMatchedString()); + ASSERT_EQ(1, parsedElement.getChildren().size()); + EXPECT_EQ(&c1, parsedElement.getChildren()[0]->getGrammarElement()); + EXPECT_EQ("", parsedElement.getChildren()[0]->getMatchedString()); + + EXPECT_EQ(&parent, parsedElement.getParent()); + EXPECT_EQ(false, parsedElement.isStopped()); + EXPECT_EQ(&r1, parsedElement.getGrammarElement()); +} + diff --git a/tests/testmain.cpp b/tests/testmain.cpp new file mode 100644 index 0000000..00f62a0 --- /dev/null +++ b/tests/testmain.cpp @@ -0,0 +1,20 @@ +// Copyright 2019 IBM Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt new file mode 100644 index 0000000..51bcf44 --- /dev/null +++ b/third_party/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8) + +add_subdirectory(googletest) +add_subdirectory(gRPC_utils) diff --git a/third_party/gRPC_utils/CMakeLists.txt b/third_party/gRPC_utils/CMakeLists.txt new file mode 100644 index 0000000..35ff1e1 --- /dev/null +++ b/third_party/gRPC_utils/CMakeLists.txt @@ -0,0 +1,70 @@ +# Copyright 2019 IBM Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required (VERSION 2.8) + +set(TARGET_NAME "reflection") +set(TARGET_SRC + proto_reflection_descriptor_database.cc + cli_call.cc + ) + +# find grpc + protobuf libs and code generators: +find_library(LIB_PROTOBUF protobuf) +find_library(LIB_GRPC grpc) +find_library(LIB_GRPC++ grpc++) +find_library(LIB_GRPC++_reflection grpc++_reflection) +find_program (PROTOC protoc) +find_program (PROTOC_GRPC_PLUGIN grpc_cpp_plugin) +set(GRPC_LIBS_REFLECTION -Wl,--no-as-needed ${LIB_GRPC++_reflection} -Wl,--as-needed ${LIB_GRPC++} ${LIB_GRPC} ${LIB_PROTOBUF}) +message(STATUS "PROTOC = ${PROTOC}") +message(STATUS "PROTOC_GRPC_PLUGIN = ${PROTOC_GRPC_PLUGIN}") +message(STATUS "DYNAMIC GRPC LINKING INFO = ${GRPC_LIBS_REFLECTION}") + +# determine proto file source and binary directories (binary directory used to +# write generated code to) +#set(PROTO_FILE_BASE_SRC_PATH ${CMAKE_SOURCE_DIR}/third_party) +set(PROTO_FILE_BASE_SRC_PATH ${CMAKE_CURRENT_SOURCE_DIR}) +string(REGEX REPLACE "^${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" PROTO_FILE_BASE_DST_PATH ${PROTO_FILE_BASE_SRC_PATH}) +message(STATUS "PROTO_FILE_BASE_SRC_PATH = ${PROTO_FILE_BASE_SRC_PATH}") +message(STATUS "PROTO_FILE_BASE_DST_PATH = ${PROTO_FILE_BASE_DST_PATH}") + +# add reflection generated code location to include path: +include_directories(${PROTO_FILE_BASE_DST_PATH}) + +# add rules for code generation: +add_custom_command( + OUTPUT reflection.pb.cc reflection.pb.hh + COMMAND ${PROTOC} -I${PROTO_FILE_BASE_SRC_PATH} --cpp_out=${PROTO_FILE_BASE_DST_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/reflection.proto + DEPENDS reflection.proto + ) +add_custom_command( + OUTPUT reflection.grpc.pb.cc reflection.grpc.pb.hh + COMMAND ${PROTOC} -I${PROTO_FILE_BASE_SRC_PATH} --grpc_out=${PROTO_FILE_BASE_DST_PATH} --plugin=protoc-gen-grpc=${PROTOC_GRPC_PLUGIN} ${CMAKE_CURRENT_SOURCE_DIR}/reflection.proto + DEPENDS reflection.proto + ) + +add_library(${TARGET_NAME} ${TARGET_SRC} + # NOTE: it is very important to list the headers here and not as dependencies + # otherwise with CMAKE 2.8 we code generation will not be triggered + reflection.pb.hh + reflection.grpc.pb.hh + ) + +# NOTE: we only need the reflection.proto generated headers here, and do not need +# to link against generated source files, as grpc libs seem to already +# bring the reflection lib +target_link_libraries(${TARGET_NAME} + ${GRPC_LIBS_REFLECTION} + ) diff --git a/third_party/gRPC_utils/LICENSE b/third_party/gRPC_utils/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/third_party/gRPC_utils/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/gRPC_utils/README b/third_party/gRPC_utils/README new file mode 100644 index 0000000..35bf494 --- /dev/null +++ b/third_party/gRPC_utils/README @@ -0,0 +1,7 @@ +Files in this directory were taken from the official gRPC repository (https://github.com/grpc/grpc). + +They include the RPC reflection interface description, as well as +very useful gRPC RPC call and reflection abtractions. + +Some minor modifications were made to those files to allow building them +in this projects (include paths). Those modifications are clearly marked. diff --git a/third_party/gRPC_utils/cli_call.cc b/third_party/gRPC_utils/cli_call.cc new file mode 100644 index 0000000..1b3d1b2 --- /dev/null +++ b/third_party/gRPC_utils/cli_call.cc @@ -0,0 +1,214 @@ +/* + * + * Copyright 2015 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// This line was modified by Rainer Schoenberger IBM +//#include "test/cpp/util/cli_call.h" +#include "cli_call.h" + +#include + +#include +#include +#include +#include +#include +#include + +namespace grpc { +namespace testing { +namespace { +void* tag(int i) { return (void*)static_cast(i); } +} // namespace + +Status CliCall::Call(std::shared_ptr channel, + const grpc::string& method, const grpc::string& request, + grpc::string* response, + const OutgoingMetadataContainer& metadata, + IncomingMetadataContainer* server_initial_metadata, + IncomingMetadataContainer* server_trailing_metadata) { + CliCall call(channel, method, metadata); + call.Write(request); + call.WritesDone(); + if (!call.Read(response, server_initial_metadata)) { + fprintf(stderr, "Failed to read response.\n"); + } + return call.Finish(server_trailing_metadata); +} + +CliCall::CliCall(std::shared_ptr channel, + const grpc::string& method, + const OutgoingMetadataContainer& metadata) + : stub_(new grpc::GenericStub(channel)) { + gpr_mu_init(&write_mu_); + gpr_cv_init(&write_cv_); + if (!metadata.empty()) { + for (OutgoingMetadataContainer::const_iterator iter = metadata.begin(); + iter != metadata.end(); ++iter) { + ctx_.AddMetadata(iter->first, iter->second); + } + } + call_ = stub_->PrepareCall(&ctx_, method, &cq_); + call_->StartCall(tag(1)); + void* got_tag; + bool ok; + cq_.Next(&got_tag, &ok); + GPR_ASSERT(ok); +} + +CliCall::~CliCall() { + gpr_cv_destroy(&write_cv_); + gpr_mu_destroy(&write_mu_); +} + +void CliCall::Write(const grpc::string& request) { + void* got_tag; + bool ok; + + gpr_slice s = gpr_slice_from_copied_buffer(request.data(), request.size()); + grpc::Slice req_slice(s, grpc::Slice::STEAL_REF); + grpc::ByteBuffer send_buffer(&req_slice, 1); + call_->Write(send_buffer, tag(2)); + cq_.Next(&got_tag, &ok); + GPR_ASSERT(ok); +} + +bool CliCall::Read(grpc::string* response, + IncomingMetadataContainer* server_initial_metadata) { + void* got_tag; + bool ok; + + grpc::ByteBuffer recv_buffer; + call_->Read(&recv_buffer, tag(3)); + + if (!cq_.Next(&got_tag, &ok) || !ok) { + return false; + } + std::vector slices; + GPR_ASSERT(recv_buffer.Dump(&slices).ok()); + + response->clear(); + for (size_t i = 0; i < slices.size(); i++) { + response->append(reinterpret_cast(slices[i].begin()), + slices[i].size()); + } + if (server_initial_metadata) { + *server_initial_metadata = ctx_.GetServerInitialMetadata(); + } + return true; +} + +void CliCall::WritesDone() { + void* got_tag; + bool ok; + + call_->WritesDone(tag(4)); + cq_.Next(&got_tag, &ok); + GPR_ASSERT(ok); +} + +void CliCall::WriteAndWait(const grpc::string& request) { + grpc::Slice req_slice(request); + grpc::ByteBuffer send_buffer(&req_slice, 1); + + gpr_mu_lock(&write_mu_); + call_->Write(send_buffer, tag(2)); + write_done_ = false; + while (!write_done_) { + gpr_cv_wait(&write_cv_, &write_mu_, gpr_inf_future(GPR_CLOCK_MONOTONIC)); + } + gpr_mu_unlock(&write_mu_); +} + +void CliCall::WritesDoneAndWait() { + gpr_mu_lock(&write_mu_); + call_->WritesDone(tag(4)); + write_done_ = false; + while (!write_done_) { + gpr_cv_wait(&write_cv_, &write_mu_, gpr_inf_future(GPR_CLOCK_MONOTONIC)); + } + gpr_mu_unlock(&write_mu_); +} + +bool CliCall::ReadAndMaybeNotifyWrite( + grpc::string* response, + IncomingMetadataContainer* server_initial_metadata) { + void* got_tag; + bool ok; + grpc::ByteBuffer recv_buffer; + + call_->Read(&recv_buffer, tag(3)); + bool cq_result = cq_.Next(&got_tag, &ok); + + while (got_tag != tag(3)) { + gpr_mu_lock(&write_mu_); + write_done_ = true; + gpr_cv_signal(&write_cv_); + gpr_mu_unlock(&write_mu_); + + cq_result = cq_.Next(&got_tag, &ok); + if (got_tag == tag(2)) { + GPR_ASSERT(ok); + } + } + + if (!cq_result || !ok) { + // If the RPC is ended on the server side, we should still wait for the + // pending write on the client side to be done. + if (!ok) { + gpr_mu_lock(&write_mu_); + if (!write_done_) { + cq_.Next(&got_tag, &ok); + GPR_ASSERT(got_tag != tag(2)); + write_done_ = true; + gpr_cv_signal(&write_cv_); + } + gpr_mu_unlock(&write_mu_); + } + return false; + } + + std::vector slices; + GPR_ASSERT(recv_buffer.Dump(&slices).ok()); + response->clear(); + for (size_t i = 0; i < slices.size(); i++) { + response->append(reinterpret_cast(slices[i].begin()), + slices[i].size()); + } + if (server_initial_metadata) { + *server_initial_metadata = ctx_.GetServerInitialMetadata(); + } + return true; +} + +Status CliCall::Finish(IncomingMetadataContainer* server_trailing_metadata) { + void* got_tag; + bool ok; + grpc::Status status; + + call_->Finish(&status, tag(5)); + cq_.Next(&got_tag, &ok); + GPR_ASSERT(ok); + if (server_trailing_metadata) { + *server_trailing_metadata = ctx_.GetServerTrailingMetadata(); + } + + return status; +} + +} // namespace testing +} // namespace grpc diff --git a/third_party/gRPC_utils/cli_call.h b/third_party/gRPC_utils/cli_call.h new file mode 100644 index 0000000..51ffafd --- /dev/null +++ b/third_party/gRPC_utils/cli_call.h @@ -0,0 +1,98 @@ +/* + * + * Copyright 2015 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef GRPC_TEST_CPP_UTIL_CLI_CALL_H +#define GRPC_TEST_CPP_UTIL_CLI_CALL_H + +#include + +#include +#include +#include +#include +#include + +namespace grpc { + +class ClientContext; + +namespace testing { + +// CliCall handles the sending and receiving of generic messages given the name +// of the remote method. This class is only used by GrpcTool. Its thread-safe +// and thread-unsafe methods should not be used together. +class CliCall final { + public: + typedef std::multimap OutgoingMetadataContainer; + typedef std::multimap + IncomingMetadataContainer; + + CliCall(std::shared_ptr channel, const grpc::string& method, + const OutgoingMetadataContainer& metadata); + ~CliCall(); + + // Perform an unary generic RPC. + static Status Call(std::shared_ptr channel, + const grpc::string& method, const grpc::string& request, + grpc::string* response, + const OutgoingMetadataContainer& metadata, + IncomingMetadataContainer* server_initial_metadata, + IncomingMetadataContainer* server_trailing_metadata); + + // Send a generic request message in a synchronous manner. NOT thread-safe. + void Write(const grpc::string& request); + + // Send a generic request message in a synchronous manner. NOT thread-safe. + void WritesDone(); + + // Receive a generic response message in a synchronous manner.NOT thread-safe. + bool Read(grpc::string* response, + IncomingMetadataContainer* server_initial_metadata); + + // Thread-safe write. Must be used with ReadAndMaybeNotifyWrite. Send out a + // generic request message and wait for ReadAndMaybeNotifyWrite to finish it. + void WriteAndWait(const grpc::string& request); + + // Thread-safe WritesDone. Must be used with ReadAndMaybeNotifyWrite. Send out + // WritesDone for gereneric request messages and wait for + // ReadAndMaybeNotifyWrite to finish it. + void WritesDoneAndWait(); + + // Thread-safe Read. Blockingly receive a generic response message. Notify + // writes if they are finished when this read is waiting for a resposne. + bool ReadAndMaybeNotifyWrite( + grpc::string* response, + IncomingMetadataContainer* server_initial_metadata); + + // Finish the RPC. + Status Finish(IncomingMetadataContainer* server_trailing_metadata); + + private: + std::unique_ptr stub_; + grpc::ClientContext ctx_; + std::unique_ptr call_; + grpc::CompletionQueue cq_; + gpr_mu write_mu_; + gpr_cv write_cv_; // Protected by write_mu_; + bool write_done_; // Portected by write_mu_; +}; + +} // namespace testing +} // namespace grpc + +#endif // GRPC_TEST_CPP_UTIL_CLI_CALL_H diff --git a/third_party/gRPC_utils/proto_reflection_descriptor_database.cc b/third_party/gRPC_utils/proto_reflection_descriptor_database.cc new file mode 100644 index 0000000..d372765 --- /dev/null +++ b/third_party/gRPC_utils/proto_reflection_descriptor_database.cc @@ -0,0 +1,333 @@ +/* + * + * Copyright 2016 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// MODIFIED by IBM (Rainer Schoenberger) +// original: #include "test/cpp/util/proto_reflection_descriptor_database.h" +#include "proto_reflection_descriptor_database.h" +// END MODIFIED + +#include + +#include + +using grpc::reflection::v1alpha::ErrorResponse; +using grpc::reflection::v1alpha::ListServiceResponse; +using grpc::reflection::v1alpha::ServerReflection; +using grpc::reflection::v1alpha::ServerReflectionRequest; +using grpc::reflection::v1alpha::ServerReflectionResponse; + +namespace grpc { + +ProtoReflectionDescriptorDatabase::ProtoReflectionDescriptorDatabase( + std::unique_ptr stub) + : stub_(std::move(stub)) {} + +ProtoReflectionDescriptorDatabase::ProtoReflectionDescriptorDatabase( + std::shared_ptr channel) + : stub_(ServerReflection::NewStub(channel)) {} + +ProtoReflectionDescriptorDatabase::~ProtoReflectionDescriptorDatabase() { + if (stream_) { + stream_->WritesDone(); + Status status = stream_->Finish(); + if (!status.ok()) { + if (status.error_code() == StatusCode::UNIMPLEMENTED) { + gpr_log(GPR_INFO, + "Reflection request not implemented; " + "is the ServerReflection service enabled?"); + } + gpr_log(GPR_INFO, + "ServerReflectionInfo rpc failed. Error code: %d, details: %s", + static_cast(status.error_code()), + status.error_message().c_str()); + } + } +} + +bool ProtoReflectionDescriptorDatabase::FindFileByName( + const string& filename, protobuf::FileDescriptorProto* output) { + if (cached_db_.FindFileByName(filename, output)) { + return true; + } + + if (known_files_.find(filename) != known_files_.end()) { + return false; + } + + ServerReflectionRequest request; + request.set_file_by_filename(filename); + ServerReflectionResponse response; + + if (!DoOneRequest(request, response)) { + return false; + } + + if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase::kFileDescriptorResponse) { + AddFileFromResponse(response.file_descriptor_response()); + } else if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase::kErrorResponse) { + const ErrorResponse error = response.error_response(); + if (error.error_code() == StatusCode::NOT_FOUND) { + gpr_log(GPR_INFO, "NOT_FOUND from server for FindFileByName(%s)", + filename.c_str()); + } else { + gpr_log(GPR_INFO, + "Error on FindFileByName(%s)\n\tError code: %d\n" + "\tError Message: %s", + filename.c_str(), error.error_code(), + error.error_message().c_str()); + } + } else { + gpr_log( + GPR_INFO, + "Error on FindFileByName(%s) response type\n" + "\tExpecting: %d\n\tReceived: %d", + filename.c_str(), + ServerReflectionResponse::MessageResponseCase::kFileDescriptorResponse, + response.message_response_case()); + } + + return cached_db_.FindFileByName(filename, output); +} + +bool ProtoReflectionDescriptorDatabase::FindFileContainingSymbol( + const string& symbol_name, protobuf::FileDescriptorProto* output) { + if (cached_db_.FindFileContainingSymbol(symbol_name, output)) { + return true; + } + + if (missing_symbols_.find(symbol_name) != missing_symbols_.end()) { + return false; + } + + ServerReflectionRequest request; + request.set_file_containing_symbol(symbol_name); + ServerReflectionResponse response; + + if (!DoOneRequest(request, response)) { + return false; + } + + if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase::kFileDescriptorResponse) { + AddFileFromResponse(response.file_descriptor_response()); + } else if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase::kErrorResponse) { + const ErrorResponse error = response.error_response(); + if (error.error_code() == StatusCode::NOT_FOUND) { + missing_symbols_.insert(symbol_name); + gpr_log(GPR_INFO, + "NOT_FOUND from server for FindFileContainingSymbol(%s)", + symbol_name.c_str()); + } else { + gpr_log(GPR_INFO, + "Error on FindFileContainingSymbol(%s)\n" + "\tError code: %d\n\tError Message: %s", + symbol_name.c_str(), error.error_code(), + error.error_message().c_str()); + } + } else { + gpr_log( + GPR_INFO, + "Error on FindFileContainingSymbol(%s) response type\n" + "\tExpecting: %d\n\tReceived: %d", + symbol_name.c_str(), + ServerReflectionResponse::MessageResponseCase::kFileDescriptorResponse, + response.message_response_case()); + } + return cached_db_.FindFileContainingSymbol(symbol_name, output); +} + +bool ProtoReflectionDescriptorDatabase::FindFileContainingExtension( + const string& containing_type, int field_number, + protobuf::FileDescriptorProto* output) { + if (cached_db_.FindFileContainingExtension(containing_type, field_number, + output)) { + return true; + } + + if (missing_extensions_.find(containing_type) != missing_extensions_.end() && + missing_extensions_[containing_type].find(field_number) != + missing_extensions_[containing_type].end()) { + gpr_log(GPR_INFO, "nested map."); + return false; + } + + ServerReflectionRequest request; + request.mutable_file_containing_extension()->set_containing_type( + containing_type); + request.mutable_file_containing_extension()->set_extension_number( + field_number); + ServerReflectionResponse response; + + if (!DoOneRequest(request, response)) { + return false; + } + + if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase::kFileDescriptorResponse) { + AddFileFromResponse(response.file_descriptor_response()); + } else if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase::kErrorResponse) { + const ErrorResponse error = response.error_response(); + if (error.error_code() == StatusCode::NOT_FOUND) { + if (missing_extensions_.find(containing_type) == + missing_extensions_.end()) { + missing_extensions_[containing_type] = {}; + } + missing_extensions_[containing_type].insert(field_number); + gpr_log(GPR_INFO, + "NOT_FOUND from server for FindFileContainingExtension(%s, %d)", + containing_type.c_str(), field_number); + } else { + gpr_log(GPR_INFO, + "Error on FindFileContainingExtension(%s, %d)\n" + "\tError code: %d\n\tError Message: %s", + containing_type.c_str(), field_number, error.error_code(), + error.error_message().c_str()); + } + } else { + gpr_log( + GPR_INFO, + "Error on FindFileContainingExtension(%s, %d) response type\n" + "\tExpecting: %d\n\tReceived: %d", + containing_type.c_str(), field_number, + ServerReflectionResponse::MessageResponseCase::kFileDescriptorResponse, + response.message_response_case()); + } + + return cached_db_.FindFileContainingExtension(containing_type, field_number, + output); +} + +bool ProtoReflectionDescriptorDatabase::FindAllExtensionNumbers( + const string& extendee_type, std::vector* output) { + if (cached_extension_numbers_.find(extendee_type) != + cached_extension_numbers_.end()) { + *output = cached_extension_numbers_[extendee_type]; + return true; + } + + ServerReflectionRequest request; + request.set_all_extension_numbers_of_type(extendee_type); + ServerReflectionResponse response; + + if (!DoOneRequest(request, response)) { + return false; + } + + if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase:: + kAllExtensionNumbersResponse) { + auto number = response.all_extension_numbers_response().extension_number(); + *output = std::vector(number.begin(), number.end()); + cached_extension_numbers_[extendee_type] = *output; + return true; + } else if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase::kErrorResponse) { + const ErrorResponse error = response.error_response(); + if (error.error_code() == StatusCode::NOT_FOUND) { + gpr_log(GPR_INFO, "NOT_FOUND from server for FindAllExtensionNumbers(%s)", + extendee_type.c_str()); + } else { + gpr_log(GPR_INFO, + "Error on FindAllExtensionNumbersExtension(%s)\n" + "\tError code: %d\n\tError Message: %s", + extendee_type.c_str(), error.error_code(), + error.error_message().c_str()); + } + } + return false; +} + +bool ProtoReflectionDescriptorDatabase::GetServices( + std::vector* output) { + ServerReflectionRequest request; + request.set_list_services(""); + ServerReflectionResponse response; + + if (!DoOneRequest(request, response)) { + return false; + } + + if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase::kListServicesResponse) { + const ListServiceResponse ls_response = response.list_services_response(); + for (int i = 0; i < ls_response.service_size(); ++i) { + (*output).push_back(ls_response.service(i).name()); + } + return true; + } else if (response.message_response_case() == + ServerReflectionResponse::MessageResponseCase::kErrorResponse) { + const ErrorResponse error = response.error_response(); + gpr_log(GPR_INFO, + "Error on GetServices()\n\tError code: %d\n" + "\tError Message: %s", + error.error_code(), error.error_message().c_str()); + } else { + gpr_log( + GPR_INFO, + "Error on GetServices() response type\n\tExpecting: %d\n\tReceived: %d", + ServerReflectionResponse::MessageResponseCase::kListServicesResponse, + response.message_response_case()); + } + return false; +} + +const protobuf::FileDescriptorProto +ProtoReflectionDescriptorDatabase::ParseFileDescriptorProtoResponse( + const grpc::string& byte_fd_proto) { + protobuf::FileDescriptorProto file_desc_proto; + file_desc_proto.ParseFromString(byte_fd_proto); + return file_desc_proto; +} + +void ProtoReflectionDescriptorDatabase::AddFileFromResponse( + const grpc::reflection::v1alpha::FileDescriptorResponse& response) { + for (int i = 0; i < response.file_descriptor_proto_size(); ++i) { + const protobuf::FileDescriptorProto file_proto = + ParseFileDescriptorProtoResponse(response.file_descriptor_proto(i)); + if (known_files_.find(file_proto.name()) == known_files_.end()) { + known_files_.insert(file_proto.name()); + cached_db_.Add(file_proto); + } + } +} + +const std::shared_ptr +ProtoReflectionDescriptorDatabase::GetStream() { + if (!stream_) { + stream_ = stub_->ServerReflectionInfo(&ctx_); + } + return stream_; +} + +bool ProtoReflectionDescriptorDatabase::DoOneRequest( + const ServerReflectionRequest& request, + ServerReflectionResponse& response) { + bool success = false; + stream_mutex_.lock(); + if (GetStream()->Write(request) && GetStream()->Read(&response)) { + success = true; + } + stream_mutex_.unlock(); + return success; +} + +} // namespace grpc diff --git a/third_party/gRPC_utils/proto_reflection_descriptor_database.h b/third_party/gRPC_utils/proto_reflection_descriptor_database.h new file mode 100644 index 0000000..0dd3784 --- /dev/null +++ b/third_party/gRPC_utils/proto_reflection_descriptor_database.h @@ -0,0 +1,114 @@ +/* + * + * Copyright 2016 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#ifndef GRPC_TEST_CPP_PROTO_SERVER_REFLECTION_DATABSE_H +#define GRPC_TEST_CPP_PROTO_SERVER_REFLECTION_DATABSE_H + +#include +#include +#include +#include + +#include +#include +// MODIFIED by IBM (Rainer Schoenberger) +// original: #include "src/proto/grpc/reflection/v1alpha/reflection.grpc.pb.h" +#include "third_party/gRPC_utils/reflection.grpc.pb.h" +// END MODIFIED + +namespace grpc { + +// ProtoReflectionDescriptorDatabase takes a stub of ServerReflection and +// provides the methods defined by DescriptorDatabase interfaces. It can be used +// to feed a DescriptorPool instance. +class ProtoReflectionDescriptorDatabase : public protobuf::DescriptorDatabase { + public: + explicit ProtoReflectionDescriptorDatabase( + std::unique_ptr stub); + + explicit ProtoReflectionDescriptorDatabase( + std::shared_ptr channel); + + virtual ~ProtoReflectionDescriptorDatabase(); + + // The following four methods implement DescriptorDatabase interfaces. + // + // Find a file by file name. Fills in in *output and returns true if found. + // Otherwise, returns false, leaving the contents of *output undefined. + bool FindFileByName(const string& filename, + protobuf::FileDescriptorProto* output) override; + + // Find the file that declares the given fully-qualified symbol name. + // If found, fills in *output and returns true, otherwise returns false + // and leaves *output undefined. + bool FindFileContainingSymbol(const string& symbol_name, + protobuf::FileDescriptorProto* output) override; + + // Find the file which defines an extension extending the given message type + // with the given field number. If found, fills in *output and returns true, + // otherwise returns false and leaves *output undefined. containing_type + // must be a fully-qualified type name. + bool FindFileContainingExtension( + const string& containing_type, int field_number, + protobuf::FileDescriptorProto* output) override; + + // Finds the tag numbers used by all known extensions of + // extendee_type, and appends them to output in an undefined + // order. This method is best-effort: it's not guaranteed that the + // database will find all extensions, and it's not guaranteed that + // FindFileContainingExtension will return true on all of the found + // numbers. Returns true if the search was successful, otherwise + // returns false and leaves output unchanged. + bool FindAllExtensionNumbers(const string& extendee_type, + std::vector* output) override; + + // Provide a list of full names of registered services + bool GetServices(std::vector* output); + + private: + typedef ClientReaderWriter< + grpc::reflection::v1alpha::ServerReflectionRequest, + grpc::reflection::v1alpha::ServerReflectionResponse> + ClientStream; + + const protobuf::FileDescriptorProto ParseFileDescriptorProtoResponse( + const grpc::string& byte_fd_proto); + + void AddFileFromResponse( + const grpc::reflection::v1alpha::FileDescriptorResponse& response); + + const std::shared_ptr GetStream(); + + bool DoOneRequest( + const grpc::reflection::v1alpha::ServerReflectionRequest& request, + grpc::reflection::v1alpha::ServerReflectionResponse& response); + + std::shared_ptr stream_; + grpc::ClientContext ctx_; + std::unique_ptr stub_; + std::unordered_set known_files_; + std::unordered_set missing_symbols_; + std::unordered_map> missing_extensions_; + std::unordered_map> cached_extension_numbers_; + std::mutex stream_mutex_; + + protobuf::SimpleDescriptorDatabase cached_db_; +}; + +} // namespace grpc + +#endif // GRPC_TEST_CPP_METRICS_SERVER_H diff --git a/third_party/gRPC_utils/reflection.proto b/third_party/gRPC_utils/reflection.proto new file mode 100644 index 0000000..816852f --- /dev/null +++ b/third_party/gRPC_utils/reflection.proto @@ -0,0 +1,136 @@ +// Copyright 2016 gRPC authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Service exported by server reflection + +syntax = "proto3"; + +package grpc.reflection.v1alpha; + +service ServerReflection { + // The reflection service is structured as a bidirectional stream, ensuring + // all related requests go to a single server. + rpc ServerReflectionInfo(stream ServerReflectionRequest) + returns (stream ServerReflectionResponse); +} + +// The message sent by the client when calling ServerReflectionInfo method. +message ServerReflectionRequest { + string host = 1; + // To use reflection service, the client should set one of the following + // fields in message_request. The server distinguishes requests by their + // defined field and then handles them using corresponding methods. + oneof message_request { + // Find a proto file by the file name. + string file_by_filename = 3; + + // Find the proto file that declares the given fully-qualified symbol name. + // This field should be a fully-qualified symbol name + // (e.g. .[.] or .). + string file_containing_symbol = 4; + + // Find the proto file which defines an extension extending the given + // message type with the given field number. + ExtensionRequest file_containing_extension = 5; + + // Finds the tag numbers used by all known extensions of the given message + // type, and appends them to ExtensionNumberResponse in an undefined order. + // Its corresponding method is best-effort: it's not guaranteed that the + // reflection service will implement this method, and it's not guaranteed + // that this method will provide all extensions. Returns + // StatusCode::UNIMPLEMENTED if it's not implemented. + // This field should be a fully-qualified type name. The format is + // . + string all_extension_numbers_of_type = 6; + + // List the full names of registered services. The content will not be + // checked. + string list_services = 7; + } +} + +// The type name and extension number sent by the client when requesting +// file_containing_extension. +message ExtensionRequest { + // Fully-qualified type name. The format should be . + string containing_type = 1; + int32 extension_number = 2; +} + +// The message sent by the server to answer ServerReflectionInfo method. +message ServerReflectionResponse { + string valid_host = 1; + ServerReflectionRequest original_request = 2; + // The server set one of the following fields accroding to the message_request + // in the request. + oneof message_response { + // This message is used to answer file_by_filename, file_containing_symbol, + // file_containing_extension requests with transitive dependencies. As + // the repeated label is not allowed in oneof fields, we use a + // FileDescriptorResponse message to encapsulate the repeated fields. + // The reflection service is allowed to avoid sending FileDescriptorProtos + // that were previously sent in response to earlier requests in the stream. + FileDescriptorResponse file_descriptor_response = 4; + + // This message is used to answer all_extension_numbers_of_type requst. + ExtensionNumberResponse all_extension_numbers_response = 5; + + // This message is used to answer list_services request. + ListServiceResponse list_services_response = 6; + + // This message is used when an error occurs. + ErrorResponse error_response = 7; + } +} + +// Serialized FileDescriptorProto messages sent by the server answering +// a file_by_filename, file_containing_symbol, or file_containing_extension +// request. +message FileDescriptorResponse { + // Serialized FileDescriptorProto messages. We avoid taking a dependency on + // descriptor.proto, which uses proto2 only features, by making them opaque + // bytes instead. + repeated bytes file_descriptor_proto = 1; +} + +// A list of extension numbers sent by the server answering +// all_extension_numbers_of_type request. +message ExtensionNumberResponse { + // Full name of the base type, including the package name. The format + // is . + string base_type_name = 1; + repeated int32 extension_number = 2; +} + +// A list of ServiceResponse sent by the server answering list_services request. +message ListServiceResponse { + // The information of each service may be expanded in the future, so we use + // ServiceResponse message to encapsulate it. + repeated ServiceResponse service = 1; +} + +// The information of a single service used by ListServiceResponse to answer +// list_services request. +message ServiceResponse { + // Full name of a registered service, including its package name. The format + // is . + string name = 1; +} + +// The error code and error message sent by the server when an error occurs. +message ErrorResponse { + // This field uses the error codes defined in grpc::StatusCode. + int32 error_code = 1; + string error_message = 2; +} diff --git a/third_party/googletest b/third_party/googletest new file mode 160000 index 0000000..8b6d3f9 --- /dev/null +++ b/third_party/googletest @@ -0,0 +1 @@ +Subproject commit 8b6d3f9c4a774bef3081195d422993323b6bb2e0