couchbase 4.2.1 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +1 -0
- package/deps/couchbase-cxx-client/.gitmodules +3 -0
- package/deps/couchbase-cxx-client/.idea/misc.xml +1 -0
- package/deps/couchbase-cxx-client/.idea/vcs.xml +1 -0
- package/deps/couchbase-cxx-client/CMakeLists.txt +11 -1
- package/deps/couchbase-cxx-client/README.md +3 -3
- package/deps/couchbase-cxx-client/cmake/CompilerWarnings.cmake +4 -1
- package/deps/couchbase-cxx-client/cmake/VersionInfo.cmake +13 -1
- package/deps/couchbase-cxx-client/cmake/build_version.hxx.in +1 -0
- package/deps/couchbase-cxx-client/core/cluster.hxx +15 -5
- package/deps/couchbase-cxx-client/core/impl/build_deferred_query_indexes.cxx +17 -6
- package/deps/couchbase-cxx-client/core/impl/cluster.cxx +1 -1
- package/deps/couchbase-cxx-client/core/impl/collection_query_index_manager.cxx +93 -0
- package/deps/couchbase-cxx-client/core/impl/configuration_profiles_registry.cxx +11 -0
- package/deps/couchbase-cxx-client/core/impl/create_query_index.cxx +119 -0
- package/deps/couchbase-cxx-client/core/impl/drop_query_index.cxx +108 -0
- package/deps/couchbase-cxx-client/core/impl/get.cxx +1 -1
- package/deps/couchbase-cxx-client/core/impl/get_all_query_indexes.cxx +76 -0
- package/deps/couchbase-cxx-client/core/impl/query.cxx +5 -7
- package/deps/couchbase-cxx-client/core/impl/watch_query_indexes.cxx +168 -0
- package/deps/couchbase-cxx-client/core/io/mcbp_session.cxx +15 -1
- package/deps/couchbase-cxx-client/core/logger/configuration.hxx +3 -0
- package/deps/couchbase-cxx-client/core/logger/level.hxx +21 -0
- package/deps/couchbase-cxx-client/core/logger/logger.hxx +4 -6
- package/deps/couchbase-cxx-client/core/meta/CMakeLists.txt +4 -2
- package/deps/couchbase-cxx-client/core/meta/features.hxx +31 -0
- package/deps/couchbase-cxx-client/core/meta/version.cxx +67 -5
- package/deps/couchbase-cxx-client/core/meta/version.hxx +12 -1
- package/deps/couchbase-cxx-client/core/metrics/CMakeLists.txt +4 -1
- package/deps/couchbase-cxx-client/core/metrics/logging_meter.cxx +46 -5
- package/deps/couchbase-cxx-client/core/metrics/logging_meter.hxx +10 -26
- package/deps/couchbase-cxx-client/core/operations/document_get_projected.cxx +3 -2
- package/deps/couchbase-cxx-client/core/operations/document_query.cxx +10 -12
- package/deps/couchbase-cxx-client/core/operations/document_query.hxx +1 -3
- package/deps/couchbase-cxx-client/core/operations/management/query_index_build.cxx +8 -14
- package/deps/couchbase-cxx-client/core/operations/management/query_index_build.hxx +2 -1
- package/deps/couchbase-cxx-client/core/operations/management/query_index_build_deferred.hxx +15 -8
- package/deps/couchbase-cxx-client/core/operations/management/query_index_create.cxx +7 -14
- package/deps/couchbase-cxx-client/core/operations/management/query_index_create.hxx +2 -0
- package/deps/couchbase-cxx-client/core/operations/management/query_index_drop.cxx +11 -16
- package/deps/couchbase-cxx-client/core/operations/management/query_index_drop.hxx +2 -0
- package/deps/couchbase-cxx-client/core/operations/management/query_index_get_all.cxx +8 -12
- package/deps/couchbase-cxx-client/core/operations/management/query_index_get_all.hxx +4 -3
- package/deps/couchbase-cxx-client/core/operations/management/query_index_get_all_deferred.cxx +21 -12
- package/deps/couchbase-cxx-client/core/operations/management/query_index_get_all_deferred.hxx +3 -2
- package/deps/couchbase-cxx-client/core/origin.hxx +1 -1
- package/deps/couchbase-cxx-client/core/platform/uuid.cc +1 -2
- package/deps/couchbase-cxx-client/core/protocol/cmd_hello.hxx +5 -1
- package/deps/couchbase-cxx-client/core/query_context.hxx +79 -0
- package/deps/couchbase-cxx-client/core/tracing/CMakeLists.txt +3 -1
- package/deps/couchbase-cxx-client/core/tracing/threshold_logging_tracer.cxx +19 -4
- package/deps/couchbase-cxx-client/core/tracing/threshold_logging_tracer.hxx +2 -2
- package/deps/couchbase-cxx-client/core/transactions/async_attempt_context.hxx +10 -4
- package/deps/couchbase-cxx-client/core/transactions/atr_cleanup_entry.cxx +52 -63
- package/deps/couchbase-cxx-client/core/transactions/attempt_context.hxx +8 -3
- package/deps/couchbase-cxx-client/core/transactions/attempt_context_impl.cxx +163 -126
- package/deps/couchbase-cxx-client/core/transactions/attempt_context_impl.hxx +24 -37
- package/deps/couchbase-cxx-client/core/transactions/forward_compat.hxx +4 -4
- package/deps/couchbase-cxx-client/core/transactions/internal/atr_cleanup_entry.hxx +51 -13
- package/deps/couchbase-cxx-client/core/transactions/internal/client_record.hxx +26 -1
- package/deps/couchbase-cxx-client/core/transactions/internal/doc_record.hxx +21 -0
- package/deps/couchbase-cxx-client/core/transactions/internal/logging.hxx +40 -18
- package/deps/couchbase-cxx-client/core/transactions/internal/transaction_context.hxx +5 -0
- package/deps/couchbase-cxx-client/core/transactions/result.hxx +26 -0
- package/deps/couchbase-cxx-client/core/transactions/staged_mutation.cxx +48 -47
- package/deps/couchbase-cxx-client/core/transactions/staged_mutation.hxx +6 -6
- package/deps/couchbase-cxx-client/core/transactions/transaction_context.cxx +33 -19
- package/deps/couchbase-cxx-client/core/transactions/transaction_get_result.hxx +18 -2
- package/deps/couchbase-cxx-client/core/transactions/transaction_links.hxx +25 -2
- package/deps/couchbase-cxx-client/core/transactions/transactions.cxx +4 -4
- package/deps/couchbase-cxx-client/core/transactions/transactions_cleanup.cxx +49 -56
- package/deps/couchbase-cxx-client/core/transactions/waitable_op_list.hxx +7 -7
- package/deps/couchbase-cxx-client/core/transactions.hxx +0 -12
- package/deps/couchbase-cxx-client/core/utils/binary.hxx +1 -1
- package/deps/couchbase-cxx-client/core/utils/keyspace.hxx +55 -0
- package/deps/couchbase-cxx-client/couchbase/build_query_index_options.hxx +12 -45
- package/deps/couchbase-cxx-client/couchbase/cluster.hxx +1 -1
- package/deps/couchbase-cxx-client/couchbase/cluster_options.hxx +6 -7
- package/deps/couchbase-cxx-client/couchbase/collection.hxx +8 -0
- package/deps/couchbase-cxx-client/couchbase/collection_query_index_manager.hxx +218 -0
- package/deps/couchbase-cxx-client/couchbase/configuration_profiles_registry.hxx +3 -0
- package/deps/couchbase-cxx-client/couchbase/create_primary_query_index_options.hxx +166 -0
- package/deps/couchbase-cxx-client/couchbase/create_query_index_options.hxx +172 -0
- package/deps/couchbase-cxx-client/couchbase/drop_primary_query_index_options.hxx +129 -0
- package/deps/couchbase-cxx-client/couchbase/drop_query_index_options.hxx +116 -0
- package/deps/couchbase-cxx-client/couchbase/fmt/cas.hxx +1 -1
- package/deps/couchbase-cxx-client/couchbase/fmt/query_scan_consistency.hxx +46 -0
- package/deps/couchbase-cxx-client/couchbase/fmt/query_status.hxx +70 -0
- package/deps/couchbase-cxx-client/couchbase/fmt/tls_verify_mode.hxx +46 -0
- package/deps/couchbase-cxx-client/couchbase/get_all_query_indexes_options.hxx +100 -0
- package/deps/couchbase-cxx-client/{core → couchbase}/management/query_index.hxx +2 -2
- package/deps/couchbase-cxx-client/couchbase/metrics/meter.hxx +16 -0
- package/deps/couchbase-cxx-client/couchbase/query_index_manager.hxx +178 -6
- package/deps/couchbase-cxx-client/couchbase/query_options.hxx +1 -18
- package/deps/couchbase-cxx-client/couchbase/scope.hxx +5 -2
- package/deps/couchbase-cxx-client/couchbase/tracing/request_tracer.hxx +16 -0
- package/deps/couchbase-cxx-client/couchbase/transactions/async_attempt_context.hxx +11 -4
- package/deps/couchbase-cxx-client/couchbase/transactions/attempt_context.hxx +5 -3
- package/deps/couchbase-cxx-client/couchbase/transactions/transaction_keyspace.hxx +16 -0
- package/deps/couchbase-cxx-client/couchbase/transactions/transaction_query_options.hxx +0 -6
- package/deps/couchbase-cxx-client/couchbase/watch_query_indexes_options.hxx +115 -0
- package/deps/couchbase-cxx-client/examples/minimal.cxx +3 -1
- package/deps/couchbase-cxx-client/test/test_integration_crud.cxx +72 -0
- package/deps/couchbase-cxx-client/test/test_integration_management.cxx +727 -310
- package/deps/couchbase-cxx-client/test/test_integration_query.cxx +4 -8
- package/deps/couchbase-cxx-client/test/test_integration_transcoders.cxx +14 -0
- package/deps/couchbase-cxx-client/test/test_transaction_transaction_public_blocking_api.cxx +34 -19
- package/deps/couchbase-cxx-client/test/test_unit_transaction_logging.cxx +66 -22
- package/deps/couchbase-cxx-client/test/test_unit_utils.cxx +51 -0
- package/deps/couchbase-cxx-client/test/tools/tool_kv_loader.cxx +2 -2
- package/deps/couchbase-cxx-client/test/utils/integration_test_guard.cxx +2 -0
- package/deps/couchbase-cxx-client/test/utils/wait_until.cxx +4 -4
- package/deps/couchbase-cxx-client/third_party/docopt/.travis.yml +103 -0
- package/deps/couchbase-cxx-client/third_party/docopt/CMakeLists.txt +129 -0
- package/deps/couchbase-cxx-client/third_party/docopt/LICENSE-Boost-1.0 +23 -0
- package/deps/couchbase-cxx-client/third_party/docopt/LICENSE-MIT +23 -0
- package/deps/couchbase-cxx-client/third_party/docopt/README.rst +479 -0
- package/deps/couchbase-cxx-client/third_party/docopt/docopt-config.cmake +1 -0
- package/deps/couchbase-cxx-client/third_party/docopt/docopt.cpp +687 -0
- package/deps/couchbase-cxx-client/third_party/docopt/docopt.h +98 -0
- package/deps/couchbase-cxx-client/third_party/docopt/docopt.pc.in +9 -0
- package/deps/couchbase-cxx-client/third_party/docopt/docopt_private.h +676 -0
- package/deps/couchbase-cxx-client/third_party/docopt/docopt_util.h +122 -0
- package/deps/couchbase-cxx-client/third_party/docopt/docopt_value.h +341 -0
- package/deps/couchbase-cxx-client/third_party/docopt/examples/naval_fate.cpp +36 -0
- package/deps/couchbase-cxx-client/third_party/docopt/main.cpp +16 -0
- package/deps/couchbase-cxx-client/third_party/docopt/run_testcase.cpp +40 -0
- package/deps/couchbase-cxx-client/third_party/docopt/run_tests.py +72 -0
- package/deps/couchbase-cxx-client/third_party/docopt/testcases.docopt +957 -0
- package/deps/couchbase-cxx-client/tools/CMakeLists.txt +14 -0
- package/deps/couchbase-cxx-client/tools/cbc.cxx +65 -0
- package/deps/couchbase-cxx-client/tools/command.hxx +31 -0
- package/deps/couchbase-cxx-client/tools/command_registry.cxx +43 -0
- package/deps/couchbase-cxx-client/tools/command_registry.hxx +39 -0
- package/deps/couchbase-cxx-client/tools/get.cxx +267 -0
- package/deps/couchbase-cxx-client/tools/get.hxx +26 -0
- package/deps/couchbase-cxx-client/tools/query.cxx +441 -0
- package/deps/couchbase-cxx-client/tools/query.hxx +26 -0
- package/deps/couchbase-cxx-client/tools/utils.cxx +418 -0
- package/deps/couchbase-cxx-client/tools/utils.hxx +150 -0
- package/deps/couchbase-cxx-client/tools/version.cxx +82 -0
- package/deps/couchbase-cxx-client/tools/version.hxx +26 -0
- package/dist/authenticators.d.ts +2 -2
- package/dist/authenticators.js +1 -2
- package/dist/binding.d.ts +32 -16
- package/dist/cluster.js +14 -7
- package/dist/collection.d.ts +6 -0
- package/dist/collection.js +8 -0
- package/dist/queryexecutor.js +1 -1
- package/dist/queryindexmanager.d.ts +100 -4
- package/dist/queryindexmanager.js +344 -118
- package/dist/transactions.js +0 -2
- package/package.json +1 -1
- package/src/connection.cpp +2 -0
- package/src/connection.hpp +1 -0
- package/src/connection_autogen.cpp +16 -0
- package/src/jstocbpp_autogen.hpp +93 -23
- package/src/jstocbpp_basic.hpp +24 -0
- package/src/jstocbpp_transactions.hpp +0 -8
- package/tools/gen-bindings-js.js +1 -0
- package/tools/gen-bindings-json.py +4 -2
- package/deps/couchbase-cxx-client/core/transactions/logging.cxx +0 -107
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
//
|
|
2
|
+
// docopt.cpp
|
|
3
|
+
// docopt
|
|
4
|
+
//
|
|
5
|
+
// Created by Jared Grubb on 2013-11-03.
|
|
6
|
+
// Copyright (c) 2013 Jared Grubb. All rights reserved.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
#include "docopt.h"
|
|
10
|
+
#include "docopt_util.h"
|
|
11
|
+
#include "docopt_private.h"
|
|
12
|
+
|
|
13
|
+
#include "docopt_value.h"
|
|
14
|
+
|
|
15
|
+
#include <vector>
|
|
16
|
+
#include <unordered_set>
|
|
17
|
+
#include <unordered_map>
|
|
18
|
+
#include <map>
|
|
19
|
+
#include <string>
|
|
20
|
+
#include <iostream>
|
|
21
|
+
#include <cassert>
|
|
22
|
+
#include <cstddef>
|
|
23
|
+
|
|
24
|
+
using namespace docopt;
|
|
25
|
+
|
|
26
|
+
DOCOPT_INLINE
|
|
27
|
+
std::ostream& docopt::operator<<(std::ostream& os, value const& val)
|
|
28
|
+
{
|
|
29
|
+
if (val.isBool()) {
|
|
30
|
+
bool b = val.asBool();
|
|
31
|
+
os << (b ? "true" : "false");
|
|
32
|
+
} else if (val.isLong()) {
|
|
33
|
+
long v = val.asLong();
|
|
34
|
+
os << v;
|
|
35
|
+
} else if (val.isString()) {
|
|
36
|
+
std::string const& str = val.asString();
|
|
37
|
+
os << '"' << str << '"';
|
|
38
|
+
} else if (val.isStringList()) {
|
|
39
|
+
auto const& list = val.asStringList();
|
|
40
|
+
os << "[";
|
|
41
|
+
bool first = true;
|
|
42
|
+
for(auto const& el : list) {
|
|
43
|
+
if (first) {
|
|
44
|
+
first = false;
|
|
45
|
+
} else {
|
|
46
|
+
os << ", ";
|
|
47
|
+
}
|
|
48
|
+
os << '"' << el << '"';
|
|
49
|
+
}
|
|
50
|
+
os << "]";
|
|
51
|
+
} else {
|
|
52
|
+
os << "null";
|
|
53
|
+
}
|
|
54
|
+
return os;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#if 0
|
|
58
|
+
#pragma mark -
|
|
59
|
+
#pragma mark Parsing stuff
|
|
60
|
+
#endif
|
|
61
|
+
|
|
62
|
+
class Tokens {
|
|
63
|
+
public:
|
|
64
|
+
Tokens(std::vector<std::string> tokens, bool isParsingArgv = true)
|
|
65
|
+
: fTokens(std::move(tokens)),
|
|
66
|
+
fIsParsingArgv(isParsingArgv)
|
|
67
|
+
{}
|
|
68
|
+
|
|
69
|
+
explicit operator bool() const {
|
|
70
|
+
return fIndex < fTokens.size();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static Tokens from_pattern(std::string const& source) {
|
|
74
|
+
static const std::regex re_separators {
|
|
75
|
+
"(?:\\s*)" // any spaces (non-matching subgroup)
|
|
76
|
+
"("
|
|
77
|
+
"[\\[\\]\\(\\)\\|]" // one character of brackets or parens or pipe character
|
|
78
|
+
"|"
|
|
79
|
+
"\\.\\.\\." // elipsis
|
|
80
|
+
")" };
|
|
81
|
+
|
|
82
|
+
static const std::regex re_strings {
|
|
83
|
+
"(?:\\s*)" // any spaces (non-matching subgroup)
|
|
84
|
+
"("
|
|
85
|
+
"\\S*<.*?>" // strings, but make sure to keep "< >" strings together
|
|
86
|
+
"|"
|
|
87
|
+
"[^<>\\s]+" // string without <>
|
|
88
|
+
")" };
|
|
89
|
+
|
|
90
|
+
// We do two stages of regex matching. The '[]()' and '...' are strong delimeters
|
|
91
|
+
// and need to be split out anywhere they occur (even at the end of a token). We
|
|
92
|
+
// first split on those, and then parse the stuff between them to find the string
|
|
93
|
+
// tokens. This is a little harder than the python version, since they have regex.split
|
|
94
|
+
// and we dont have anything like that.
|
|
95
|
+
|
|
96
|
+
std::vector<std::string> tokens;
|
|
97
|
+
std::for_each(std::sregex_iterator{ source.begin(), source.end(), re_separators },
|
|
98
|
+
std::sregex_iterator{},
|
|
99
|
+
[&](std::smatch const& match)
|
|
100
|
+
{
|
|
101
|
+
// handle anything before the separator (this is the "stuff" between the delimeters)
|
|
102
|
+
if (match.prefix().matched) {
|
|
103
|
+
std::for_each(std::sregex_iterator{match.prefix().first, match.prefix().second, re_strings},
|
|
104
|
+
std::sregex_iterator{},
|
|
105
|
+
[&](std::smatch const& m)
|
|
106
|
+
{
|
|
107
|
+
tokens.push_back(m[1].str());
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// handle the delimter token itself
|
|
112
|
+
if (match[1].matched) {
|
|
113
|
+
tokens.push_back(match[1].str());
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return Tokens(tokens, false);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
std::string const& current() const {
|
|
121
|
+
if (*this)
|
|
122
|
+
return fTokens[fIndex];
|
|
123
|
+
|
|
124
|
+
static std::string const empty;
|
|
125
|
+
return empty;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
std::string the_rest() const {
|
|
129
|
+
if (!*this)
|
|
130
|
+
return {};
|
|
131
|
+
return join(fTokens.begin()+static_cast<std::ptrdiff_t>(fIndex),
|
|
132
|
+
fTokens.end(),
|
|
133
|
+
" ");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
std::string pop() {
|
|
137
|
+
return std::move(fTokens.at(fIndex++));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
bool isParsingArgv() const { return fIsParsingArgv; }
|
|
141
|
+
|
|
142
|
+
struct OptionError : std::runtime_error { using runtime_error::runtime_error; };
|
|
143
|
+
|
|
144
|
+
private:
|
|
145
|
+
std::vector<std::string> fTokens;
|
|
146
|
+
size_t fIndex = 0;
|
|
147
|
+
bool fIsParsingArgv;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Get all instances of 'T' from the pattern
|
|
151
|
+
template <typename T>
|
|
152
|
+
std::vector<T*> flat_filter(Pattern& pattern) {
|
|
153
|
+
std::vector<Pattern*> flattened = pattern.flat([](Pattern const* p) -> bool {
|
|
154
|
+
return dynamic_cast<T const*>(p) != nullptr;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// now, we're guaranteed to have T*'s, so just use static_cast
|
|
158
|
+
std::vector<T*> ret;
|
|
159
|
+
std::transform(flattened.begin(), flattened.end(), std::back_inserter(ret), [](Pattern* p) {
|
|
160
|
+
return static_cast<T*>(p);
|
|
161
|
+
});
|
|
162
|
+
return ret;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
static std::vector<std::string> parse_section(std::string const& name, std::string const& source) {
|
|
166
|
+
// ECMAScript regex only has "?=" for a non-matching lookahead. In order to make sure we always have
|
|
167
|
+
// a newline to anchor our matching, we have to avoid matching the final newline of each grouping.
|
|
168
|
+
// Therefore, our regex is adjusted from the docopt Python one to use ?= to match the newlines before
|
|
169
|
+
// the following lines, rather than after.
|
|
170
|
+
std::regex const re_section_pattern {
|
|
171
|
+
"(?:^|\\n)" // anchored at a linebreak (or start of string)
|
|
172
|
+
"("
|
|
173
|
+
"[^\\n]*" + name + "[^\\n]*(?=\\n?)" // a line that contains the name
|
|
174
|
+
"(?:\\n[ \\t].*?(?=\\n|$))*" // followed by any number of lines that are indented
|
|
175
|
+
")",
|
|
176
|
+
std::regex::icase
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
std::vector<std::string> ret;
|
|
180
|
+
std::for_each(std::sregex_iterator(source.begin(), source.end(), re_section_pattern),
|
|
181
|
+
std::sregex_iterator(),
|
|
182
|
+
[&](std::smatch const& match)
|
|
183
|
+
{
|
|
184
|
+
ret.push_back(trim(match[1].str()));
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return ret;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
static bool is_argument_spec(std::string const& token) {
|
|
191
|
+
if (token.empty())
|
|
192
|
+
return false;
|
|
193
|
+
|
|
194
|
+
if (token[0]=='<' && token[token.size()-1]=='>')
|
|
195
|
+
return true;
|
|
196
|
+
|
|
197
|
+
if (std::all_of(token.begin(), token.end(), &::isupper))
|
|
198
|
+
return true;
|
|
199
|
+
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
template <typename I>
|
|
204
|
+
std::vector<std::string> longOptions(I iter, I end) {
|
|
205
|
+
std::vector<std::string> ret;
|
|
206
|
+
std::transform(iter, end,
|
|
207
|
+
std::back_inserter(ret),
|
|
208
|
+
[](typename I::reference opt) { return opt->longOption(); });
|
|
209
|
+
return ret;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
static PatternList parse_long(Tokens& tokens, std::vector<Option>& options)
|
|
213
|
+
{
|
|
214
|
+
// long ::= '--' chars [ ( ' ' | '=' ) chars ] ;
|
|
215
|
+
std::string longOpt, equal;
|
|
216
|
+
value val;
|
|
217
|
+
std::tie(longOpt, equal, val) = partition(tokens.pop(), "=");
|
|
218
|
+
|
|
219
|
+
assert(starts_with(longOpt, "--"));
|
|
220
|
+
|
|
221
|
+
if (equal.empty()) {
|
|
222
|
+
val = value{};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// detect with options match this long option
|
|
226
|
+
std::vector<Option const*> similar;
|
|
227
|
+
for(auto const& option : options) {
|
|
228
|
+
if (option.longOption()==longOpt)
|
|
229
|
+
similar.push_back(&option);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// maybe allow similar options that match by prefix
|
|
233
|
+
if (tokens.isParsingArgv() && similar.empty()) {
|
|
234
|
+
for(auto const& option : options) {
|
|
235
|
+
if (option.longOption().empty())
|
|
236
|
+
continue;
|
|
237
|
+
if (starts_with(option.longOption(), longOpt))
|
|
238
|
+
similar.push_back(&option);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
PatternList ret;
|
|
243
|
+
|
|
244
|
+
if (similar.size() > 1) { // might be simply specified ambiguously 2+ times?
|
|
245
|
+
std::vector<std::string> prefixes = longOptions(similar.begin(), similar.end());
|
|
246
|
+
std::string error = "'" + longOpt + "' is not a unique prefix: ";
|
|
247
|
+
error.append(join(prefixes.begin(), prefixes.end(), ", "));
|
|
248
|
+
throw Tokens::OptionError(std::move(error));
|
|
249
|
+
} else if (similar.empty()) {
|
|
250
|
+
int argcount = equal.empty() ? 0 : 1;
|
|
251
|
+
options.emplace_back("", longOpt, argcount);
|
|
252
|
+
|
|
253
|
+
auto o = std::make_shared<Option>(options.back());
|
|
254
|
+
if (tokens.isParsingArgv()) {
|
|
255
|
+
o->setValue(argcount ? value{val} : value{true});
|
|
256
|
+
}
|
|
257
|
+
ret.push_back(o);
|
|
258
|
+
} else {
|
|
259
|
+
auto o = std::make_shared<Option>(*similar[0]);
|
|
260
|
+
if (o->argCount() == 0) {
|
|
261
|
+
if (val) {
|
|
262
|
+
std::string error = o->longOption() + " must not have an argument";
|
|
263
|
+
throw Tokens::OptionError(std::move(error));
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
if (!val) {
|
|
267
|
+
auto const& token = tokens.current();
|
|
268
|
+
if (token.empty() || token=="--") {
|
|
269
|
+
std::string error = o->longOption() + " requires an argument";
|
|
270
|
+
throw Tokens::OptionError(std::move(error));
|
|
271
|
+
}
|
|
272
|
+
val = tokens.pop();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (tokens.isParsingArgv()) {
|
|
276
|
+
o->setValue(val ? std::move(val) : value{true});
|
|
277
|
+
}
|
|
278
|
+
ret.push_back(o);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return ret;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
static PatternList parse_short(Tokens& tokens, std::vector<Option>& options)
|
|
285
|
+
{
|
|
286
|
+
// shorts ::= '-' ( chars )* [ [ ' ' ] chars ] ;
|
|
287
|
+
|
|
288
|
+
auto token = tokens.pop();
|
|
289
|
+
|
|
290
|
+
assert(starts_with(token, "-"));
|
|
291
|
+
assert(!starts_with(token, "--"));
|
|
292
|
+
|
|
293
|
+
auto i = token.begin();
|
|
294
|
+
++i; // skip the leading '-'
|
|
295
|
+
|
|
296
|
+
PatternList ret;
|
|
297
|
+
while (i != token.end()) {
|
|
298
|
+
std::string shortOpt = { '-', *i };
|
|
299
|
+
++i;
|
|
300
|
+
|
|
301
|
+
std::vector<Option const*> similar;
|
|
302
|
+
for(auto const& option : options) {
|
|
303
|
+
if (option.shortOption()==shortOpt)
|
|
304
|
+
similar.push_back(&option);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (similar.size() > 1) {
|
|
308
|
+
std::string error = shortOpt + " is specified ambiguously "
|
|
309
|
+
+ std::to_string(similar.size()) + " times";
|
|
310
|
+
throw Tokens::OptionError(std::move(error));
|
|
311
|
+
} else if (similar.empty()) {
|
|
312
|
+
options.emplace_back(shortOpt, "", 0);
|
|
313
|
+
|
|
314
|
+
auto o = std::make_shared<Option>(options.back());
|
|
315
|
+
if (tokens.isParsingArgv()) {
|
|
316
|
+
o->setValue(value{true});
|
|
317
|
+
}
|
|
318
|
+
ret.push_back(o);
|
|
319
|
+
} else {
|
|
320
|
+
auto o = std::make_shared<Option>(*similar[0]);
|
|
321
|
+
value val;
|
|
322
|
+
if (o->argCount()) {
|
|
323
|
+
if (i == token.end()) {
|
|
324
|
+
// consume the next token
|
|
325
|
+
auto const& ttoken = tokens.current();
|
|
326
|
+
if (ttoken.empty() || ttoken=="--") {
|
|
327
|
+
std::string error = shortOpt + " requires an argument";
|
|
328
|
+
throw Tokens::OptionError(std::move(error));
|
|
329
|
+
}
|
|
330
|
+
val = tokens.pop();
|
|
331
|
+
} else {
|
|
332
|
+
// consume all the rest
|
|
333
|
+
val = std::string{i, token.end()};
|
|
334
|
+
i = token.end();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (tokens.isParsingArgv()) {
|
|
339
|
+
o->setValue(val ? std::move(val) : value{true});
|
|
340
|
+
}
|
|
341
|
+
ret.push_back(o);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return ret;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
static PatternList parse_expr(Tokens& tokens, std::vector<Option>& options);
|
|
349
|
+
|
|
350
|
+
static PatternList parse_atom(Tokens& tokens, std::vector<Option>& options)
|
|
351
|
+
{
|
|
352
|
+
// atom ::= '(' expr ')' | '[' expr ']' | 'options'
|
|
353
|
+
// | long | shorts | argument | command ;
|
|
354
|
+
|
|
355
|
+
std::string const& token = tokens.current();
|
|
356
|
+
|
|
357
|
+
PatternList ret;
|
|
358
|
+
|
|
359
|
+
if (token == "[") {
|
|
360
|
+
tokens.pop();
|
|
361
|
+
|
|
362
|
+
auto expr = parse_expr(tokens, options);
|
|
363
|
+
|
|
364
|
+
auto trailing = tokens.pop();
|
|
365
|
+
if (trailing != "]") {
|
|
366
|
+
throw DocoptLanguageError("Mismatched '['");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
ret.emplace_back(std::make_shared<Optional>(std::move(expr)));
|
|
370
|
+
} else if (token=="(") {
|
|
371
|
+
tokens.pop();
|
|
372
|
+
|
|
373
|
+
auto expr = parse_expr(tokens, options);
|
|
374
|
+
|
|
375
|
+
auto trailing = tokens.pop();
|
|
376
|
+
if (trailing != ")") {
|
|
377
|
+
throw DocoptLanguageError("Mismatched '('");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
ret.emplace_back(std::make_shared<Required>(std::move(expr)));
|
|
381
|
+
} else if (token == "options") {
|
|
382
|
+
tokens.pop();
|
|
383
|
+
ret.emplace_back(std::make_shared<OptionsShortcut>());
|
|
384
|
+
} else if (starts_with(token, "--") && token != "--") {
|
|
385
|
+
ret = parse_long(tokens, options);
|
|
386
|
+
} else if (starts_with(token, "-") && token != "-" && token != "--") {
|
|
387
|
+
ret = parse_short(tokens, options);
|
|
388
|
+
} else if (is_argument_spec(token)) {
|
|
389
|
+
ret.emplace_back(std::make_shared<Argument>(tokens.pop()));
|
|
390
|
+
} else {
|
|
391
|
+
ret.emplace_back(std::make_shared<Command>(tokens.pop()));
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return ret;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
static PatternList parse_seq(Tokens& tokens, std::vector<Option>& options)
|
|
398
|
+
{
|
|
399
|
+
// seq ::= ( atom [ '...' ] )* ;"""
|
|
400
|
+
|
|
401
|
+
PatternList ret;
|
|
402
|
+
|
|
403
|
+
while (tokens) {
|
|
404
|
+
auto const& token = tokens.current();
|
|
405
|
+
|
|
406
|
+
if (token=="]" || token==")" || token=="|")
|
|
407
|
+
break;
|
|
408
|
+
|
|
409
|
+
auto atom = parse_atom(tokens, options);
|
|
410
|
+
if (tokens.current() == "...") {
|
|
411
|
+
ret.emplace_back(std::make_shared<OneOrMore>(std::move(atom)));
|
|
412
|
+
tokens.pop();
|
|
413
|
+
} else {
|
|
414
|
+
std::move(atom.begin(), atom.end(), std::back_inserter(ret));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return ret;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
static std::shared_ptr<Pattern> maybe_collapse_to_required(PatternList&& seq)
|
|
422
|
+
{
|
|
423
|
+
if (seq.size()==1) {
|
|
424
|
+
return std::move(seq[0]);
|
|
425
|
+
}
|
|
426
|
+
return std::make_shared<Required>(std::move(seq));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
static std::shared_ptr<Pattern> maybe_collapse_to_either(PatternList&& seq)
|
|
430
|
+
{
|
|
431
|
+
if (seq.size()==1) {
|
|
432
|
+
return std::move(seq[0]);
|
|
433
|
+
}
|
|
434
|
+
return std::make_shared<Either>(std::move(seq));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
PatternList parse_expr(Tokens& tokens, std::vector<Option>& options)
|
|
438
|
+
{
|
|
439
|
+
// expr ::= seq ( '|' seq )* ;
|
|
440
|
+
|
|
441
|
+
auto seq = parse_seq(tokens, options);
|
|
442
|
+
|
|
443
|
+
if (tokens.current() != "|")
|
|
444
|
+
return seq;
|
|
445
|
+
|
|
446
|
+
PatternList ret;
|
|
447
|
+
ret.emplace_back(maybe_collapse_to_required(std::move(seq)));
|
|
448
|
+
|
|
449
|
+
while (tokens.current() == "|") {
|
|
450
|
+
tokens.pop();
|
|
451
|
+
seq = parse_seq(tokens, options);
|
|
452
|
+
ret.emplace_back(maybe_collapse_to_required(std::move(seq)));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return { maybe_collapse_to_either(std::move(ret)) };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
static Required parse_pattern(std::string const& source, std::vector<Option>& options)
|
|
459
|
+
{
|
|
460
|
+
auto tokens = Tokens::from_pattern(source);
|
|
461
|
+
auto result = parse_expr(tokens, options);
|
|
462
|
+
|
|
463
|
+
if (tokens)
|
|
464
|
+
throw DocoptLanguageError("Unexpected ending: '" + tokens.the_rest() + "'");
|
|
465
|
+
|
|
466
|
+
assert(result.size() == 1 && "top level is always one big");
|
|
467
|
+
return Required{ std::move(result) };
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
static std::string formal_usage(std::string const& section) {
|
|
472
|
+
std::string ret = "(";
|
|
473
|
+
|
|
474
|
+
auto i = section.find(':')+1; // skip past "usage:"
|
|
475
|
+
auto parts = split(section, i);
|
|
476
|
+
for(size_t ii = 1; ii < parts.size(); ++ii) {
|
|
477
|
+
if (parts[ii] == parts[0]) {
|
|
478
|
+
ret += " ) | (";
|
|
479
|
+
} else {
|
|
480
|
+
ret.push_back(' ');
|
|
481
|
+
ret += parts[ii];
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
ret += " )";
|
|
486
|
+
return ret;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
static PatternList parse_argv(Tokens tokens, std::vector<Option>& options, bool options_first)
|
|
490
|
+
{
|
|
491
|
+
// Parse command-line argument vector.
|
|
492
|
+
//
|
|
493
|
+
// If options_first:
|
|
494
|
+
// argv ::= [ long | shorts ]* [ argument ]* [ '--' [ argument ]* ] ;
|
|
495
|
+
// else:
|
|
496
|
+
// argv ::= [ long | shorts | argument ]* [ '--' [ argument ]* ] ;
|
|
497
|
+
|
|
498
|
+
PatternList ret;
|
|
499
|
+
while (tokens) {
|
|
500
|
+
auto const& token = tokens.current();
|
|
501
|
+
|
|
502
|
+
if (token=="--") {
|
|
503
|
+
// option list is done; convert all the rest to arguments
|
|
504
|
+
while (tokens) {
|
|
505
|
+
ret.emplace_back(std::make_shared<Argument>("", tokens.pop()));
|
|
506
|
+
}
|
|
507
|
+
} else if (starts_with(token, "--")) {
|
|
508
|
+
auto&& parsed = parse_long(tokens, options);
|
|
509
|
+
std::move(parsed.begin(), parsed.end(), std::back_inserter(ret));
|
|
510
|
+
} else if (token[0]=='-' && token != "-") {
|
|
511
|
+
auto&& parsed = parse_short(tokens, options);
|
|
512
|
+
std::move(parsed.begin(), parsed.end(), std::back_inserter(ret));
|
|
513
|
+
} else if (options_first) {
|
|
514
|
+
// option list is done; convert all the rest to arguments
|
|
515
|
+
while (tokens) {
|
|
516
|
+
ret.emplace_back(std::make_shared<Argument>("", tokens.pop()));
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
ret.emplace_back(std::make_shared<Argument>("", tokens.pop()));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return ret;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
static std::vector<Option> parse_defaults(std::string const& doc) {
|
|
527
|
+
// This pattern is a delimiter by which we split the options.
|
|
528
|
+
// The delimiter is a new line followed by a whitespace(s) followed by one or two hyphens.
|
|
529
|
+
static std::regex const re_delimiter{
|
|
530
|
+
"(?:^|\\n)[ \\t]*" // a new line with leading whitespace
|
|
531
|
+
"(?=-{1,2})" // [split happens here] (positive lookahead) ... and followed by one or two hyphes
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
std::vector<Option> defaults;
|
|
535
|
+
for (auto s : parse_section("options:", doc)) {
|
|
536
|
+
s.erase(s.begin(), s.begin() + static_cast<std::ptrdiff_t>(s.find(':')) + 1); // get rid of "options:"
|
|
537
|
+
|
|
538
|
+
for (const auto& opt : regex_split(s, re_delimiter)) {
|
|
539
|
+
if (starts_with(opt, "-")) {
|
|
540
|
+
defaults.emplace_back(Option::parse(opt));
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return defaults;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
static bool isOptionSet(PatternList const& options, std::string const& opt1, std::string const& opt2 = "") {
|
|
549
|
+
return std::any_of(options.begin(), options.end(), [&](std::shared_ptr<Pattern const> const& opt) -> bool {
|
|
550
|
+
auto const& name = opt->name();
|
|
551
|
+
if (name==opt1 || (!opt2.empty() && name==opt2)) {
|
|
552
|
+
return opt->hasValue();
|
|
553
|
+
}
|
|
554
|
+
return false;
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
static void extras(bool help, bool version, PatternList const& options) {
|
|
559
|
+
if (help && isOptionSet(options, "-h", "--help")) {
|
|
560
|
+
throw DocoptExitHelp();
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (version && isOptionSet(options, "--version")) {
|
|
564
|
+
throw DocoptExitVersion();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Parse the doc string and generate the Pattern tree
|
|
569
|
+
static std::pair<Required, std::vector<Option>> create_pattern_tree(std::string const& doc)
|
|
570
|
+
{
|
|
571
|
+
auto usage_sections = parse_section("usage:", doc);
|
|
572
|
+
if (usage_sections.empty()) {
|
|
573
|
+
throw DocoptLanguageError("'usage:' (case-insensitive) not found.");
|
|
574
|
+
}
|
|
575
|
+
if (usage_sections.size() > 1) {
|
|
576
|
+
throw DocoptLanguageError("More than one 'usage:' (case-insensitive).");
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
std::vector<Option> options = parse_defaults(doc);
|
|
580
|
+
Required pattern = parse_pattern(formal_usage(usage_sections[0]), options);
|
|
581
|
+
|
|
582
|
+
std::vector<Option const*> pattern_options = flat_filter<Option const>(pattern);
|
|
583
|
+
|
|
584
|
+
using UniqueOptions = std::unordered_set<Option const*, PatternHasher, PatternPointerEquality>;
|
|
585
|
+
UniqueOptions const uniq_pattern_options { pattern_options.begin(), pattern_options.end() };
|
|
586
|
+
|
|
587
|
+
// Fix up any "[options]" shortcuts with the actual option tree
|
|
588
|
+
for(auto& options_shortcut : flat_filter<OptionsShortcut>(pattern)) {
|
|
589
|
+
std::vector<Option> doc_options = parse_defaults(doc);
|
|
590
|
+
|
|
591
|
+
// set(doc_options) - set(pattern_options)
|
|
592
|
+
UniqueOptions uniq_doc_options;
|
|
593
|
+
for(auto const& opt : doc_options) {
|
|
594
|
+
if (uniq_pattern_options.count(&opt))
|
|
595
|
+
continue;
|
|
596
|
+
uniq_doc_options.insert(&opt);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// turn into shared_ptr's and set as children
|
|
600
|
+
PatternList children;
|
|
601
|
+
std::transform(uniq_doc_options.begin(), uniq_doc_options.end(),
|
|
602
|
+
std::back_inserter(children), [](Option const* opt) {
|
|
603
|
+
return std::make_shared<Option>(*opt);
|
|
604
|
+
});
|
|
605
|
+
options_shortcut->setChildren(std::move(children));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return { std::move(pattern), std::move(options) };
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
DOCOPT_INLINE
|
|
612
|
+
docopt::Options
|
|
613
|
+
docopt::docopt_parse(std::string const& doc,
|
|
614
|
+
std::vector<std::string> const& argv,
|
|
615
|
+
bool help,
|
|
616
|
+
bool version,
|
|
617
|
+
bool options_first)
|
|
618
|
+
{
|
|
619
|
+
Required pattern;
|
|
620
|
+
std::vector<Option> options;
|
|
621
|
+
try {
|
|
622
|
+
std::tie(pattern, options) = create_pattern_tree(doc);
|
|
623
|
+
} catch (Tokens::OptionError const& error) {
|
|
624
|
+
throw DocoptLanguageError(error.what());
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
PatternList argv_patterns;
|
|
628
|
+
try {
|
|
629
|
+
argv_patterns = parse_argv(Tokens(argv), options, options_first);
|
|
630
|
+
} catch (Tokens::OptionError const& error) {
|
|
631
|
+
throw DocoptArgumentError(error.what());
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
extras(help, version, argv_patterns);
|
|
635
|
+
|
|
636
|
+
std::vector<std::shared_ptr<LeafPattern>> collected;
|
|
637
|
+
bool matched = pattern.fix().match(argv_patterns, collected);
|
|
638
|
+
if (matched && argv_patterns.empty()) {
|
|
639
|
+
docopt::Options ret;
|
|
640
|
+
|
|
641
|
+
// (a.name, a.value) for a in (pattern.flat() + collected)
|
|
642
|
+
for (auto* p : pattern.leaves()) {
|
|
643
|
+
ret[p->name()] = p->getValue();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
for (auto const& p : collected) {
|
|
647
|
+
ret[p->name()] = p->getValue();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return ret;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (matched) {
|
|
654
|
+
std::string leftover = join(argv.begin(), argv.end(), ", ");
|
|
655
|
+
throw DocoptArgumentError("Unexpected argument: " + leftover);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
throw DocoptArgumentError("Arguments did not match expected patterns"); // BLEH. Bad error.
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
DOCOPT_INLINE
|
|
662
|
+
docopt::Options
|
|
663
|
+
docopt::docopt(std::string const& doc,
|
|
664
|
+
std::vector<std::string> const& argv,
|
|
665
|
+
bool help,
|
|
666
|
+
std::string const& version,
|
|
667
|
+
bool options_first) noexcept
|
|
668
|
+
{
|
|
669
|
+
try {
|
|
670
|
+
return docopt_parse(doc, argv, help, !version.empty(), options_first);
|
|
671
|
+
} catch (DocoptExitHelp const&) {
|
|
672
|
+
std::cout << doc << std::endl;
|
|
673
|
+
std::exit(0);
|
|
674
|
+
} catch (DocoptExitVersion const&) {
|
|
675
|
+
std::cout << version << std::endl;
|
|
676
|
+
std::exit(0);
|
|
677
|
+
} catch (DocoptLanguageError const& error) {
|
|
678
|
+
std::cerr << "Docopt usage string could not be parsed" << std::endl;
|
|
679
|
+
std::cerr << error.what() << std::endl;
|
|
680
|
+
std::exit(-1);
|
|
681
|
+
} catch (DocoptArgumentError const& error) {
|
|
682
|
+
std::cerr << error.what();
|
|
683
|
+
std::cout << std::endl;
|
|
684
|
+
std::cout << doc << std::endl;
|
|
685
|
+
std::exit(-1);
|
|
686
|
+
} /* Any other exception is unexpected: let std::terminate grab it */
|
|
687
|
+
}
|