summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorShaun Verch <shaun.verch@10gen.com>2013-07-13 12:59:25 -0400
committerShaun Verch <shaun.verch@10gen.com>2013-08-09 12:19:55 -0400
commitee0b4a412fbcd8bb60afd6321c2c934387f40d46 (patch)
tree706cd2b9ed90b4b692a7292502808be45348a166 /src/mongo
parent7ef3ff6668c05a44745e62d02c40b6041df80bf3 (diff)
downloadmongo-ee0b4a412fbcd8bb60afd6321c2c934387f40d46.tar.gz
SERVER-8510 Added command line and config file parser to source tree with unit tests
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/SConscript3
-rw-r--r--src/mongo/base/error_codes.err1
-rw-r--r--src/mongo/db/json.cpp11
-rw-r--r--src/mongo/util/options_parser/SConscript19
-rw-r--r--src/mongo/util/options_parser/constraints.cpp74
-rw-r--r--src/mongo/util/options_parser/constraints.h124
-rw-r--r--src/mongo/util/options_parser/environment.cpp151
-rw-r--r--src/mongo/util/options_parser/environment.h165
-rw-r--r--src/mongo/util/options_parser/environment_test.cpp81
-rw-r--r--src/mongo/util/options_parser/option_description.h80
-rw-r--r--src/mongo/util/options_parser/option_section.cpp240
-rw-r--r--src/mongo/util/options_parser/option_section.h110
-rw-r--r--src/mongo/util/options_parser/options_parser.cpp611
-rw-r--r--src/mongo/util/options_parser/options_parser.h115
-rw-r--r--src/mongo/util/options_parser/options_parser_test.cpp930
-rw-r--r--src/mongo/util/options_parser/test_config_files/empty.json0
-rw-r--r--src/mongo/util/options_parser/test_config_files/good.conf1
-rw-r--r--src/mongo/util/options_parser/test_config_files/good.json3
-rw-r--r--src/mongo/util/options_parser/value.cpp158
-rw-r--r--src/mongo/util/options_parser/value.h125
20 files changed, 3000 insertions, 2 deletions
diff --git a/src/mongo/SConscript b/src/mongo/SConscript
index db8eefcda55..fd108dd4906 100644
--- a/src/mongo/SConscript
+++ b/src/mongo/SConscript
@@ -26,7 +26,8 @@ env.SConscript(['base/SConscript',
'platform/SConscript',
's/SConscript',
'unittest/SConscript',
- 'util/concurrency/SConscript'])
+ 'util/concurrency/SConscript',
+ 'util/options_parser/SConscript'])
def add_exe( v ):
return "${PROGPREFIX}%s${PROGSUFFIX}" % v
diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err
index 6465bed8e83..8a51b1ec267 100644
--- a/src/mongo/base/error_codes.err
+++ b/src/mongo/base/error_codes.err
@@ -44,5 +44,6 @@ error_code("ConflictingUpdateOperators", 40)
error_code("FileAlreadyOpen", 41)
error_code("LogWriteFailed", 42)
error_code("CursorNotFound", 43)
+error_code("KeyNotFound", 44)
error_class("NetworkError", ["HostUnreachable", "HostNotFound"])
diff --git a/src/mongo/db/json.cpp b/src/mongo/db/json.cpp
index 3f7135f8095..9992ced57a1 100644
--- a/src/mongo/db/json.cpp
+++ b/src/mongo/db/json.cpp
@@ -987,7 +987,16 @@ namespace mongo {
}
JParse jparse(jsonString);
BSONObjBuilder builder;
- Status ret = jparse.object("UNUSED", builder, false);
+ Status ret = Status::OK();
+ try {
+ ret = jparse.object("UNUSED", builder, false);
+ }
+ catch(std::exception& e) {
+ std::ostringstream message;
+ message << "caught exception from within JSON parser: " << e.what();
+ throw MsgAssertionException(17031, message.str());
+ }
+
if (ret != Status::OK()) {
ostringstream message;
message << "code " << ret.code() << ": " << ret.codeString() << ": " << ret.reason();
diff --git a/src/mongo/util/options_parser/SConscript b/src/mongo/util/options_parser/SConscript
new file mode 100644
index 00000000000..ee574dff8bf
--- /dev/null
+++ b/src/mongo/util/options_parser/SConscript
@@ -0,0 +1,19 @@
+# -*- mode: python -*-
+
+Import("env")
+
+env.StaticLibrary('options_parser', ['environment.cpp',
+ 'value.cpp',
+ 'constraints.cpp',
+ 'option_section.cpp',
+ 'options_parser.cpp',
+ ],
+ LIBDEPS=['$BUILD_DIR/mongo/bson'])
+
+env.CppUnitTest('options_parser_test',
+ 'options_parser_test.cpp',
+ LIBDEPS=['options_parser', '$BUILD_DIR/mongo/unittest/unittest'])
+
+env.CppUnitTest('environment_test',
+ 'environment_test.cpp',
+ LIBDEPS=['options_parser', '$BUILD_DIR/mongo/unittest/unittest'])
diff --git a/src/mongo/util/options_parser/constraints.cpp b/src/mongo/util/options_parser/constraints.cpp
new file mode 100644
index 00000000000..2186c8c4fb5
--- /dev/null
+++ b/src/mongo/util/options_parser/constraints.cpp
@@ -0,0 +1,74 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 "mongo/util/options_parser/constraints.h"
+
+#include "mongo/base/status.h"
+#include "mongo/bson/util/builder.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ Status NumericKeyConstraint::check(const Environment& env) {
+ Value val;
+ Status s = env.get(_key, &val);
+
+ if (s == ErrorCodes::KeyNotFound) {
+ // Key not set, skipping numeric constraint check
+ return Status::OK();
+ }
+
+ // The code that controls whether a type is "compatible" is contained in the Value
+ // class, so if that handles compatibility between numeric types then this will too.
+ long intVal;
+ if (val.get(&intVal).isOK()) {
+ if (intVal < _min || intVal > _max) {
+ StringBuilder sb;
+ sb << "Error: Attempting to set " << _key << " to value: " <<
+ intVal << " which is out of range: (" <<
+ _min << "," << _max << ")";
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ }
+ else {
+ StringBuilder sb;
+ sb << "Error: " << _key << " is of type: " << val.typeToString() <<
+ " but must be of a numeric type.";
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ return Status::OK();
+ }
+
+ Status ImmutableKeyConstraint::check(const Environment& env) {
+ Value env_value;
+ Status ret = env.get(_key, &env_value);
+ if (ret.isOK()) {
+ if (_value.isEmpty()) {
+ _value = env_value;
+ }
+ else {
+ if (!_value.equal(env_value)) {
+ StringBuilder sb;
+ sb << "Error: " << _key << " is immutable once set";
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ }
+ }
+
+ return ret;
+ }
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/constraints.h b/src/mongo/util/options_parser/constraints.h
new file mode 100644
index 00000000000..9d111c340ef
--- /dev/null
+++ b/src/mongo/util/options_parser/constraints.h
@@ -0,0 +1,124 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 "mongo/base/status.h"
+#include "mongo/bson/util/builder.h"
+#include "mongo/util/options_parser/environment.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ /** A Constraint validates an Environment. It has one function, which takes an Environment as
+ * an argument and returns either a success or failure Status depending on whether the
+ * Environment was valid according to this constraint
+ *
+ * These are meant to be registered with an Environment to define what it means for that
+ * Environment to be "valid" and run as part of its validation process
+ */
+ class Constraint {
+ public:
+ // Interface
+ Status operator()(const Environment& env) { return check(env); }
+ virtual ~Constraint() {}
+ private:
+ // Implementation
+ virtual Status check(const Environment&) = 0;
+ };
+
+ /** A KeyConstraint is a Constraint on a specific Key. Currently this is not handled any
+ * differently than a Constraint, and is only here as a way to help document whether a
+ * Constraint applies to a single Key or an Environment as a whole
+ */
+ class KeyConstraint : public Constraint {
+ public:
+ KeyConstraint(const Key& key) :
+ _key(key)
+ { }
+ virtual ~KeyConstraint() {}
+ protected:
+ Key _key;
+ };
+
+ /** Implementation of a Constraint on the range of a numeric Value. Fails if the Value is not a
+ * number, or if it is a number but outside the given range
+ */
+ class NumericKeyConstraint : public KeyConstraint {
+ public:
+ NumericKeyConstraint(const Key& k, long min, long max) :
+ KeyConstraint(k),
+ _min(min),
+ _max(max)
+ { }
+ virtual ~NumericKeyConstraint() {}
+
+ private:
+ virtual Status check(const Environment& env);
+ long _min;
+ long _max;
+ };
+
+ /** Implementation of a Constraint that makes a Value immutable. Fails if the Value has already
+ * been set and we are attempting to set it to a different Value. Note that setting it to the
+ * same value is allowed in this implementation
+ */
+ class ImmutableKeyConstraint : public KeyConstraint {
+ public:
+ ImmutableKeyConstraint(const Key& k) : KeyConstraint(k)
+ { }
+ virtual ~ImmutableKeyConstraint() {}
+
+ private:
+ virtual Status check(const Environment& env);
+ Value _value;
+ };
+
+ /** Implementation of a Constraint on the type of a Value. Fails if we cannot extract the given
+ * type from our Value, which means the implementation of the access functions of Value
+ * controls which types are "compatible"
+ */
+ template <typename T>
+ class TypeKeyConstraint : public KeyConstraint {
+ public:
+ TypeKeyConstraint(const Key& k) :
+ KeyConstraint(k)
+ { }
+ virtual ~TypeKeyConstraint() {}
+
+ private:
+ virtual Status check(const Environment& env) {
+ Value val;
+ Status s = env.get(_key, &val);
+ if (!s.isOK()) {
+ // Key not set, skipping type constraint check
+ return Status::OK();
+ }
+
+ // The code that controls whether a type is "compatible" is contained in the Value
+ // class, so if that handles compatibility between numeric types then this will too.
+ T typedVal;
+ if (!val.get(&typedVal).isOK()) {
+ StringBuilder sb;
+ sb << "Error: value for key: " << _key << " was found as type: "
+ << val.typeToString() << " but is required to be type: " << typeid(typedVal).name();
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ return Status::OK();
+ }
+ };
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/environment.cpp b/src/mongo/util/options_parser/environment.cpp
new file mode 100644
index 00000000000..0db43abef4a
--- /dev/null
+++ b/src/mongo/util/options_parser/environment.cpp
@@ -0,0 +1,151 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 "mongo/util/options_parser/environment.h"
+
+#include <boost/shared_ptr.hpp>
+#include <iostream>
+
+#include "mongo/bson/util/builder.h"
+#include "mongo/util/options_parser/constraints.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ // Environment implementation
+
+ Status Environment::addKeyConstraint(KeyConstraint* c) {
+ keyConstraints.push_back(boost::shared_ptr<KeyConstraint>(c));
+ return Status::OK();
+ }
+ Status Environment::addConstraint(Constraint* c) {
+ constraints.push_back(boost::shared_ptr<Constraint>(c));
+ return Status::OK();
+ }
+
+ /** Get the value at Key. Note that we should not be able to add empty values to the
+ * environment, so we don't check for that here */
+ Status Environment::get(const Key& get_key, Value* get_value) const {
+ typedef std::map<Key, Value>::const_iterator it_type;
+ it_type value = values.find(get_key);
+ if (value == values.end()) {
+ StringBuilder sb;
+ sb << "Value not found for key: " << get_key;
+ return Status(ErrorCodes::KeyNotFound, sb.str());
+ }
+ else {
+ *get_value = value->second;
+ return Status::OK();
+ }
+ }
+
+ /** Set the Value in our Environment. Always disallow empty values */
+ Status Environment::set(const Key& add_key, const Value& add_value) {
+
+ // 1. Make sure value is not empty
+ if (add_value.isEmpty()) {
+ return Status(ErrorCodes::InternalError, "Attempted to add an empty value");
+ }
+
+ // 2. Save old values
+ std::map <Key, Value> old_values = values;
+
+ // 3. Add value to be added
+ values[add_key] = add_value;
+
+ // 4. Validate only if our environment is already valid
+ if (valid) {
+ Status ret = validate();
+ if (!ret.isOK()) {
+ // 5. Revert to old values if this was invalid
+ values = old_values;
+ return ret;
+ }
+ }
+
+ return Status::OK();
+ }
+
+ /** Set all the Values from the source Environment in our Environment. Does not check for empty
+ * values as the source Environment should not have been allowed to have any */
+ Status Environment::setAll(const Environment& add_environment) {
+
+ // 1. Save old values
+ std::map <Key, Value> old_values = values;
+
+ // 2. Add values to be added
+ std::map <Key, Value> add_values = add_environment.values;
+ for(std::map<Key, Value>::const_iterator iterator = add_values.begin();
+ iterator != add_values.end(); iterator++) {
+ values[iterator->first] = iterator->second;
+ }
+
+ // 3. Validate only if our environment is already valid
+ if (valid) {
+ Status ret = validate();
+ if (!ret.isOK()) {
+ // 4. Revert to old values if this was invalid
+ values = old_values;
+ return ret;
+ }
+ }
+
+ return Status::OK();
+ }
+
+ /** Validate the Environment by iterating over all our constraints and calling them on our
+ * Environment
+ */
+ Status Environment::validate() {
+
+ // 1. Iterate and check all KeyConstraints
+ typedef std::vector<boost::shared_ptr<KeyConstraint> >::iterator it_keyConstraint;
+ for(it_keyConstraint iterator = keyConstraints.begin();
+ iterator != keyConstraints.end(); iterator++) {
+ Status ret = (*(*iterator).get())(*this);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ }
+
+ // 2. Iterate and check all Constraints
+ typedef std::vector<boost::shared_ptr<Constraint> >::iterator it_constraint;
+ for(it_constraint iterator = constraints.begin();
+ iterator != constraints.end(); iterator++) {
+ Status ret = (*(*iterator).get())(*this);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ }
+
+ // 3. Our Environment is now valid. Record this and return success
+ valid = true;
+ return Status::OK();
+ }
+
+ /* Debugging */
+ void Environment::dump() {
+ std::map<Key, Value>::iterator iter;
+ for (iter = values.begin(); iter != values.end(); ++iter) {
+ std::cout << "Key: '"
+ << iter->first
+ << "', Value: '"
+ << iter->second.toString()
+ << "'" << std::endl;
+ }
+ }
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/environment.h b/src/mongo/util/options_parser/environment.h
new file mode 100644
index 00000000000..15725d08814
--- /dev/null
+++ b/src/mongo/util/options_parser/environment.h
@@ -0,0 +1,165 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 <boost/shared_ptr.hpp>
+#include <map>
+#include <vector>
+
+#include "mongo/base/status.h"
+#include "mongo/util/options_parser/value.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ class Constraint;
+ class KeyConstraint;
+
+ typedef std::string Key;
+
+ /** An Environment is a map of values that can be validated according to a set of registered
+ * constraints
+ *
+ * Usage overview:
+ *
+ * 1. Create an empty Environment
+ * 2. Add Constraints
+ * 3. Set Key/Value pairs (will not cause constraints to be triggered)
+ * 4. Validate (will run all constraints)
+ * 5. Access
+ * 6. Set/Modify Key/Value pairs (will run all constraints and reject invalid modifications)
+ * 7. Access
+ *
+ * Since the constraints are run whenever we try to set or modify Key/Value pairs after we
+ * validate, we have the invariant that the Environment is always valid according to its
+ * Constraints after validation. Adding new constraints is disallowed after validation.
+ *
+ * Usage example:
+ *
+ * // Create an empty Environment
+ * Environment environment;
+ *
+ * // Initialize our first Key and Value
+ * Key key1("key1");
+ * Value value1(1);
+ *
+ * // Add a Constraint on "key1"
+ * Status ret = environment.addConstraint(new ImmutableKeyConstraint("key1"));
+ * if (!ret.isOK()) {
+ * return ret;
+ * }
+ *
+ * // Set our first Key and Value to the Environment
+ * ret = environment.set(key1, value1);
+ * if (!ret.isOK()) {
+ * return ret;
+ * }
+ *
+ * // Attempt to mutate should be successful, since validate has not been called
+ * ret = environment.set(key1, Value(2));
+ * if (!ret.isOK()) {
+ * return ret;
+ * }
+ *
+ * // Validate our Environment
+ * ret = environment.validate();
+ * if (!ret.isOK()) {
+ * return ret;
+ * }
+ *
+ * // Access our Environment
+ * int intvalue1;
+ * ret = environment.get(key1, &intvalue1);
+ * if (!ret.isOK()) {
+ * return ret;
+ * }
+ *
+ * // Attempt to mutate should fail, since validate has been called
+ * ret = environment.set(key1, Value(3));
+ * if (!ret.isOK()) {
+ * return ret;
+ * }
+ */
+
+ class Environment {
+ public:
+ Environment() : valid(false) { }
+ ~Environment() { }
+
+ /** These functions are to add Constraints and KeyConstraints which will be run against
+ * this environment in the following situations:
+ * 1. in the "validate" function
+ * 2. in the "set" function after validate has been called successfully
+ *
+ * It is an error to call these functions after "validate" has been called
+ *
+ * WARNING: These take ownership of the pointer passed in
+ */
+ Status addKeyConstraint(KeyConstraint* keyConstraint);
+ Status addConstraint(Constraint* constraint);
+
+ /** Add the Value to this Environment with the given Key. If "validate" has already
+ * been called on this Environment, runs all Constraints on the new Environment. If
+ * any of the Constraints fail, reverts to the old Environment and returns an error
+ */
+ Status set(const Key& key, const Value& value);
+
+ /** Populate the given Value with the Value stored for the given Key. Return a success
+ * status if the value was found, or an error status if the value was not found.
+ * Leaves the Value unchanged on error.
+ */
+ Status get(const Key& key, Value* value) const;
+
+ /** Same as the above get interface, but supports directly getting C++ types without the
+ * intermediate Value and has the added failure case of the value being the wrong type
+ */
+ template <typename T>
+ Status get(const Key& key, T* value_contents) const;
+
+ /** Runs all registered Constraints and returns the result. On success, marks this as a
+ * valid Environment so that any modifications will re run all Constraints
+ */
+ Status validate();
+
+ /** Sets all variables in the given Environment in this Environment. Does not add
+ * Constraints
+ */
+ Status setAll(const Environment& other);
+
+ /* Debugging */
+ void dump();
+
+ protected:
+ std::vector<boost::shared_ptr<Constraint> > constraints;
+ std::vector<boost::shared_ptr<KeyConstraint> > keyConstraints;
+ std::map <Key, Value> values;
+ bool valid;
+ };
+
+ template <typename T>
+ Status Environment::get(const Key& get_key, T* get_value) const {
+ typedef std::map<Key, Value>::const_iterator it_type;
+ it_type value = values.find(get_key);
+ if (value == values.end()) {
+ return Status(ErrorCodes::InternalError, "Value not found!");
+ }
+ else {
+ return value->second.get(get_value);
+ }
+ }
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/environment_test.cpp b/src/mongo/util/options_parser/environment_test.cpp
new file mode 100644
index 00000000000..d92a9f682ff
--- /dev/null
+++ b/src/mongo/util/options_parser/environment_test.cpp
@@ -0,0 +1,81 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 "mongo/bson/util/builder.h"
+
+#include "mongo/util/options_parser/constraints.h"
+#include "mongo/util/options_parser/environment.h"
+#include "mongo/unittest/unittest.h"
+
+namespace {
+
+ using mongo::ErrorCodes;
+ using mongo::Status;
+
+ namespace moe = mongo::optionenvironment;
+
+ TEST(Environment, EmptyValue) {
+ moe::Environment environment;
+ ASSERT_NOT_OK(environment.set(moe::Key("empty"), moe::Value()));
+ }
+
+ TEST(Environment, Immutable) {
+ moe::Environment environment;
+ environment.addKeyConstraint(new moe::ImmutableKeyConstraint(moe::Key("port")));
+ ASSERT_OK(environment.set(moe::Key("port"), moe::Value(5)));
+ ASSERT_OK(environment.validate());
+ ASSERT_NOT_OK(environment.set(moe::Key("port"), moe::Value(0)));
+ }
+
+ TEST(Environment, OutOfRange) {
+ moe::Environment environment;
+ environment.addKeyConstraint(new moe::NumericKeyConstraint(moe::Key("port"), 1000, 65535));
+ ASSERT_OK(environment.validate());
+ ASSERT_NOT_OK(environment.set(moe::Key("port"), moe::Value(0)));
+ }
+
+ TEST(Environment, NonNumericRangeConstraint) {
+ moe::Environment environment;
+ environment.addKeyConstraint(new moe::NumericKeyConstraint(moe::Key("port"), 1000, 65535));
+ ASSERT_OK(environment.validate());
+ ASSERT_NOT_OK(environment.set(moe::Key("port"), moe::Value("string")));
+ }
+
+ TEST(Environment, BadType) {
+ moe::Environment environment;
+ environment.addKeyConstraint(new moe::TypeKeyConstraint<int>(moe::Key("port")));
+ ASSERT_OK(environment.set(moe::Key("port"), moe::Value("string")));
+ ASSERT_NOT_OK(environment.validate());
+ }
+
+ TEST(Environment, AllowNumeric) {
+ moe::Environment environment;
+ environment.addKeyConstraint(new moe::TypeKeyConstraint<long>(moe::Key("port")));
+ environment.addKeyConstraint(new moe::TypeKeyConstraint<int>(moe::Key("port")));
+ ASSERT_OK(environment.set(moe::Key("port"), moe::Value(1)));
+ ASSERT_OK(environment.validate());
+ }
+
+ TEST(Environment, DirectTypeAccess) {
+ moe::Environment environment;
+ ASSERT_OK(environment.set(moe::Key("number"), moe::Value(5)));
+ std::string notNumber;
+ ASSERT_NOT_OK(environment.get(moe::Key("number"), &notNumber));
+ int number;
+ ASSERT_OK(environment.get(moe::Key("number"), &number));
+ ASSERT_EQUALS(number, 5);
+ }
+
+} // unnamed namespace
diff --git a/src/mongo/util/options_parser/option_description.h b/src/mongo/util/options_parser/option_description.h
new file mode 100644
index 00000000000..08d99122243
--- /dev/null
+++ b/src/mongo/util/options_parser/option_description.h
@@ -0,0 +1,80 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 <iostream>
+
+#include "mongo/base/status.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ /**
+ * An OptionType is an enum of all the types we support in the OptionsParser
+ */
+ enum OptionType {
+ StringVector, // po::value< std::vector<std::string> >
+ Bool, // po::value<bool>
+ Double, // po::value<double>
+ Int, // po::value<int>
+ Long, // po::value<long>
+ String, // po::value<std::string>
+ UnsignedLongLong, // po::value<unsigned long long>
+ Unsigned, // po::value<unsigned>
+ Switch // po::bool_switch
+ };
+
+ /**
+ * The OptionDescription and PositionalOptionDescription classes are containers for information
+ * about the options we are expecting either on the command line or in config files. These
+ * should be registered in an OptionSection instance and passed to an OptionsParser.
+ */
+ class OptionDescription {
+ public:
+ OptionDescription(const std::string& dottedName, // Used for JSON config and in Environment
+ const std::string& singleName, // Used for boost command line and INI
+ const OptionType type,
+ const std::string& description,
+ const bool isVisible = true)
+ : _dottedName(dottedName),
+ _singleName(singleName),
+ _type(type),
+ _description(description),
+ _isVisible(isVisible) { }
+
+ std::string _dottedName;
+ std::string _singleName;
+ OptionType _type;
+ std::string _description;
+ bool _isVisible;
+ };
+
+ class PositionalOptionDescription {
+ public:
+ PositionalOptionDescription(const std::string& name,
+ const OptionType type,
+ int count = 1)
+ : _name(name),
+ _type(type),
+ _count(count) { }
+
+ std::string _name;
+ OptionType _type;
+ int _count; // Max number of times this option could be specified. -1 = unlimited
+ };
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/option_section.cpp b/src/mongo/util/options_parser/option_section.cpp
new file mode 100644
index 00000000000..acd381c4bb5
--- /dev/null
+++ b/src/mongo/util/options_parser/option_section.cpp
@@ -0,0 +1,240 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 "mongo/util/options_parser/option_section.h"
+
+#include <sstream>
+
+#include "mongo/bson/util/builder.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ // Registration interface
+
+ Status OptionSection::addSection(const OptionSection& subSection) {
+ if (!subSection._positionalOptions.empty()) {
+ return Status(ErrorCodes::InternalError,
+ "Attempted to add subsection with positional options");
+ }
+ _subSections.push_back(subSection);
+ return Status::OK();
+ }
+
+ Status OptionSection::addOption(const OptionDescription& option) {
+ // Verify that neither the single name nor the dotted name for this option conflicts with
+ // the names for any options we have already registered
+ std::vector<OptionDescription>::const_iterator oditerator;
+ for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) {
+ if (option._dottedName == oditerator->_dottedName) {
+ StringBuilder sb;
+ sb << "Attempted to register option with duplicate dottedName: "
+ << option._dottedName;
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ if (option._singleName == oditerator->_singleName) {
+ StringBuilder sb;
+ sb << "Attempted to register option with duplicate singleName: "
+ << option._singleName;
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ }
+ _options.push_back(option);
+ return Status::OK();
+ }
+
+ Status OptionSection::addPositionalOption(const PositionalOptionDescription& positionalOption) {
+ // Verify that the name for this positional option does not conflict with the name for any
+ // positional option we have already registered
+ std::vector<PositionalOptionDescription>::const_iterator poditerator;
+ for (poditerator = _positionalOptions.begin();
+ poditerator != _positionalOptions.end(); poditerator++) {
+ if (positionalOption._name == poditerator->_name) {
+ StringBuilder sb;
+ sb << "Attempted to register duplicate positional option: "
+ << positionalOption._name;
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ }
+ Status ret = addOption(OptionDescription(positionalOption._name,
+ positionalOption._name,
+ positionalOption._type,
+ "hidden description",
+ false/*hidden*/));
+ if (!ret.isOK()) {
+ return ret;
+ }
+ _positionalOptions.push_back(positionalOption);
+ return Status::OK();
+ }
+
+ // Stuff for dealing with Boost
+
+ namespace {
+ /** Helper function to convert the values of our OptionType enum into the classes that
+ * boost::program_option uses to pass around this information
+ */
+ po::value_semantic* typeToBoostType(OptionType type) {
+ switch (type) {
+ case StringVector: return po::value< std::vector<std::string> >();
+ case Bool: return po::value<bool>();
+ case Double: return po::value<double>();
+ case Int: return po::value<int>();
+ case Long: return po::value<long>();
+ case String: return po::value<std::string>();
+ case UnsignedLongLong: return po::value<unsigned long long>();
+ case Unsigned: return po::value<unsigned>();
+ case Switch: return po::bool_switch();
+ default: return NULL; /* XXX: should not get here */
+ }
+ }
+ } // namespace
+
+ Status OptionSection::getBoostOptions(po::options_description* boostOptions,
+ bool visibleOnly) const {
+
+ std::vector<OptionDescription>::const_iterator oditerator;
+ for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) {
+ if (!visibleOnly || (oditerator->_isVisible)) {
+ boostOptions->add_options()(oditerator->_singleName.c_str(),
+ typeToBoostType(oditerator->_type),
+ oditerator->_description.c_str());
+ }
+ }
+
+ std::vector<OptionSection>::const_iterator ositerator;
+ for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) {
+ po::options_description subGroup = ositerator->_name.empty()
+ ? po::options_description()
+ : po::options_description(ositerator->_name.c_str());
+ ositerator->getBoostOptions(&subGroup, visibleOnly);
+ boostOptions->add(subGroup);
+ }
+
+ return Status::OK();
+ }
+
+ Status OptionSection::getBoostPositionalOptions(
+ po::positional_options_description* boostPositionalOptions) const {
+
+ std::vector<PositionalOptionDescription>::const_iterator poditerator;
+ for (poditerator = _positionalOptions.begin();
+ poditerator != _positionalOptions.end(); poditerator++) {
+ boostPositionalOptions->add(poditerator->_name.c_str(), poditerator->_count);
+ }
+
+ return Status::OK();
+ }
+
+ // Get options for iterating
+ // TODO: should I make this an iterator?
+
+ Status OptionSection::getAllOptions(std::vector<OptionDescription>* options) const {
+
+ std::vector<OptionDescription>::const_iterator oditerator;
+ for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) {
+ options->push_back(*oditerator);
+ }
+
+ std::vector<OptionSection>::const_iterator ositerator;
+ for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) {
+ ositerator->getAllOptions(options);
+ }
+
+ return Status::OK();
+ }
+
+ std::string OptionSection::positionalHelpString(const std::string& execName) const {
+
+ po::positional_options_description boostPositionalOptions;
+ Status ret = getBoostPositionalOptions(&boostPositionalOptions);
+ if (!ret.isOK()) {
+ StringBuilder sb;
+ sb << "Error constructing help string: " << ret.toString();
+ return sb.str();
+ }
+
+ StringBuilder posHelpStringBuilder;
+ posHelpStringBuilder << execName;
+
+ // If we can have unlimited positional options, this returns
+ // std::numeric_limits<unsigned>::max(). Check here for that case and record what name to
+ // look for.
+ unsigned int numPositional = boostPositionalOptions.max_total_count();
+ std::string trailingPositionName;
+ if (numPositional == std::numeric_limits<unsigned>::max()) {
+ trailingPositionName = boostPositionalOptions.name_for_position(numPositional - 1);
+ }
+
+ unsigned int position;
+ std::string positionName;
+ for (position = 0; position < numPositional; position++) {
+ positionName = boostPositionalOptions.name_for_position(position);
+ if (!trailingPositionName.empty() && trailingPositionName == positionName) {
+ // If we have a trailing position, we break when we see it the first time.
+ posHelpStringBuilder << " [" << trailingPositionName << " ... ]";
+ break;
+ }
+ posHelpStringBuilder << " [" << positionName << "]";
+ }
+
+ return posHelpStringBuilder.str();
+ }
+
+ std::string OptionSection::helpString() const {
+
+ po::options_description boostOptions;
+ Status ret = getBoostOptions(&boostOptions);
+ if (!ret.isOK()) {
+ StringBuilder sb;
+ sb << "Error constructing help string: " << ret.toString();
+ return sb.str();
+ }
+
+ // Can't use a StringBuilder here because boost::program_options only has functions that
+ // output to std::ostream
+ std::ostringstream os;
+ os << boostOptions;
+ return os.str();
+ }
+
+ /* Debugging */
+ void OptionSection::dump() const {
+ std::vector<PositionalOptionDescription>::const_iterator poditerator;
+ for (poditerator = _positionalOptions.begin();
+ poditerator != _positionalOptions.end(); poditerator++) {
+ std::cout << " _name: " << poditerator->_name
+ << " _type: " << poditerator->_type
+ << " _count: " << poditerator->_count << std::endl;
+ }
+
+ std::vector<OptionDescription>::const_iterator oditerator;
+ for (oditerator = _options.begin(); oditerator != _options.end(); oditerator++) {
+ std::cout << " _dottedName: " << oditerator->_dottedName
+ << " _singleName: " << oditerator->_singleName
+ << " _type: " << oditerator->_type
+ << " _description: " << oditerator->_description
+ << " _isVisible: " << oditerator->_isVisible << std::endl;
+ }
+
+ std::vector<OptionSection>::const_iterator ositerator;
+ for (ositerator = _subSections.begin(); ositerator != _subSections.end(); ositerator++) {
+ std::cout << "Section Name: " << ositerator->_name << std::endl;
+ ositerator->dump();
+ }
+ }
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/option_section.h b/src/mongo/util/options_parser/option_section.h
new file mode 100644
index 00000000000..86e0a9d2e54
--- /dev/null
+++ b/src/mongo/util/options_parser/option_section.h
@@ -0,0 +1,110 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 "mongo/util/options_parser/option_description.h"
+
+#include <boost/program_options.hpp>
+#include <iostream>
+
+#include "mongo/base/status.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ namespace po = boost::program_options;
+
+ /** A container for OptionDescription instances and PositionalOptionDescription instances as
+ * well as other OptionSection instances. Provides a description of all options that are
+ * supported to be passed in to an OptionsParser. Has utility functions to support the various
+ * formats needed by the parsing process
+ *
+ * Usage:
+ *
+ * namespace moe = mongo::optionenvironment;
+ *
+ * moe::OptionsParser parser;
+ * moe::Environment environment;
+ * moe::OptionSection options;
+ * moe::OptionSection subSection("Section Name");
+ *
+ * // Register our allowed option flags with our OptionSection
+ * options.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display Help"));
+ *
+ * // Register our positional options with our OptionSection
+ * options.addPositionalOption(moe::PositionalOptionDescription("command", moe::String));
+ *
+ * // Add a subsection
+ * subSection.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+ * options.addSection(subSection);
+ *
+ * // Run the parser
+ * Status ret = parser.run(options, argc, argv, envp, &environment);
+ * if (!ret.isOK()) {
+ * cerr << options.helpString() << endl;
+ * exit(EXIT_FAILURE);
+ * }
+ */
+
+ class OptionSection {
+ public:
+ OptionSection(const std::string& name) : _name(name) { }
+ OptionSection() { }
+
+ // Construction interface
+
+ /**
+ * Add a sub section to this section. Used mainly to keep track of section headers for when
+ * we need generate the help string for the command line
+ */
+ Status addSection(const OptionSection& subSection);
+ /**
+ * Add an option to this section
+ */
+ Status addOption(const OptionDescription& option);
+ /**
+ * Add a positional option to this section. Also adds a normal hidden option with the same
+ * name as the PositionalOptionDescription because that is the mechanism boost program
+ * options uses. Unfortunately this means that positional options can also be accessed by
+ * name in the config files and via command line flags
+ */
+ Status addPositionalOption(const PositionalOptionDescription& positionalOption);
+
+ // These functions are used by the OptionsParser to make calls into boost::program_options
+ Status getBoostOptions(po::options_description* boostOptions,
+ bool visibleOnly = false) const;
+ Status getBoostPositionalOptions(
+ po::positional_options_description* boostPositionalOptions) const;
+
+ // This is needed so that the parser can iterate over all registered options to get the
+ // correct names when populating the Environment, as well as check that a parameter that was
+ // found has been registered and has the correct type
+ Status getAllOptions(std::vector<OptionDescription>* options) const;
+
+ std::string positionalHelpString(const std::string& execName) const;
+ std::string helpString() const;
+
+ // Debugging
+ void dump() const;
+
+ private:
+ std::string _name;
+ std::vector<OptionSection> _subSections;
+ std::vector<OptionDescription> _options;
+ std::vector<PositionalOptionDescription> _positionalOptions;
+ };
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/options_parser.cpp b/src/mongo/util/options_parser/options_parser.cpp
new file mode 100644
index 00000000000..9e0d007b46c
--- /dev/null
+++ b/src/mongo/util/options_parser/options_parser.cpp
@@ -0,0 +1,611 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 "mongo/util/options_parser/options_parser.h"
+
+#include <boost/program_options.hpp>
+#include <boost/shared_ptr.hpp>
+#include <cerrno>
+#include <fstream>
+#include <stdio.h>
+
+#include "mongo/base/status.h"
+#include "mongo/bson/bsonobjiterator.h"
+#include "mongo/db/json.h"
+#include "mongo/util/options_parser/environment.h"
+#include "mongo/util/options_parser/constraints.h"
+#include "mongo/util/options_parser/option_description.h"
+#include "mongo/util/options_parser/option_section.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ using namespace std;
+
+ namespace po = boost::program_options;
+
+ namespace {
+
+ // The following section contains utility functions that convert between the various objects
+ // we need to deal with while parsing command line options.
+ //
+ // These conversions are different depending on the data source because our current
+ // implementation uses boost::program_options for the command line and INI files and the
+ // mongo JSON parser for JSON config files. Our destination storage in both cases is an
+ // Environment which stores Value objects.
+ //
+ // 1. JSON Config Files
+ // The mongo JSON parser parses a JSON string into a BSON object. Therefore, we need:
+ // a. A function to convert a BSONElement to a Value (BSONElementToValue)
+ // b. A function to iterate a BSONObj, convert the BSONElements to Values, and add
+ // them to our Environment (addBSONElementsToEnvironment)
+ //
+ // 2. INI Config Files and command line
+ // The boost::program_options parsers store their output in a
+ // boost::program_options::variables_map. Therefore, we need:
+ // a. A function to convert a boost::any to a Value (boostAnyToValue)
+ // b. A function to iterate a variables_map, convert the boost::any elements to
+ // Values, and add them to our Environment (addBoostVariablesToEnvironment)
+
+ // Convert a boost::any to a Value. See comments at the beginning of this section.
+ Status boostAnyToValue(const boost::any& anyValue, Value* value) {
+ try {
+ if (anyValue.type() == typeid(std::vector<std::string>)) {
+ *value = Value(boost::any_cast<std::vector<std::string> >(anyValue));
+ }
+ else if (anyValue.type() == typeid(bool)) {
+ *value = Value(boost::any_cast<bool>(anyValue));
+ }
+ else if (anyValue.type() == typeid(double)) {
+ *value = Value(boost::any_cast<double>(anyValue));
+ }
+ else if (anyValue.type() == typeid(int)) {
+ *value = Value(boost::any_cast<int>(anyValue));
+ }
+ else if (anyValue.type() == typeid(long)) {
+ *value = Value(boost::any_cast<long>(anyValue));
+ }
+ else if (anyValue.type() == typeid(std::string)) {
+ *value = Value(boost::any_cast<std::string>(anyValue));
+ }
+ else if (anyValue.type() == typeid(unsigned long long)) {
+ *value = Value(boost::any_cast<unsigned long long>(anyValue));
+ }
+ else if (anyValue.type() == typeid(unsigned)) {
+ *value = Value(boost::any_cast<unsigned>(anyValue));
+ }
+ else {
+ StringBuilder sb;
+ sb << "Unrecognized type: " << anyValue.type().name() <<
+ " in any to Value conversion";
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ }
+ catch(const boost::bad_any_cast& e) {
+ StringBuilder sb;
+ // We already checked the type, so this is just a sanity check
+ sb << "boost::any_cast threw exception: " << e.what();
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ return Status::OK();
+ }
+
+ // Add all the values in the given variables_map to our environment. See comments at the
+ // beginning of this section.
+ Status addBoostVariablesToEnvironment(const po::variables_map& vm,
+ const OptionSection& options,
+ Environment* environment) {
+
+ std::vector<OptionDescription> options_vector;
+ Status ret = options.getAllOptions(&options_vector);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ for(std::vector<OptionDescription>::const_iterator iterator = options_vector.begin();
+ iterator != options_vector.end(); iterator++) {
+ if (vm.count(iterator->_singleName)) {
+ Value optionValue;
+ Status ret = boostAnyToValue(vm[iterator->_singleName].value(), &optionValue);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ environment->set(iterator->_dottedName, optionValue);
+ }
+ }
+ return Status::OK();
+ }
+
+ // Check if the given key is registered in our OptionDescription. This is needed for JSON
+ // Config File handling since the JSON parser just reads in whatever fields and values it
+ // sees without taking a description of what to look for.
+ Status isRegistered(const std::vector<OptionDescription>& options_vector, const Key& key) {
+
+ for(std::vector<OptionDescription>::const_iterator iterator = options_vector.begin();
+ iterator != options_vector.end(); iterator++) {
+ if (key == iterator->_dottedName) {
+ return Status::OK();
+ }
+ }
+ StringBuilder sb;
+ sb << "Unrecognized option: " << key;
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+
+ // Convert a BSONElement to a Value. See comments at the beginning of this section.
+ Status BSONElementToValue(const BSONElement& element, Value* value) {
+
+ std::vector<BSONElement> elements;
+ std::vector<std::string> valueStrings;
+ try {
+ switch (element.type()) {
+ case ::mongo::NumberInt:
+ *value = Value(element.Int());
+ return Status::OK();
+ case ::mongo::NumberDouble:
+ *value = Value(element.Double());
+ return Status::OK();
+ case ::mongo::NumberLong:
+ // FIXME: Figure out how to stop this, or detect overflow
+ *value = Value(static_cast<unsigned long long>(element.Long()));
+ return Status::OK();
+ case ::mongo::String:
+ *value = Value(element.String());
+ return Status::OK();
+ case ::mongo::Array:
+ elements = element.Array();
+ for(std::vector<BSONElement>::const_iterator iterator = elements.begin();
+ iterator != elements.end(); iterator++) {
+ if (iterator->type() == ::mongo::String) {
+ valueStrings.push_back(iterator->String());
+ }
+ else {
+ StringBuilder sb;
+ sb << "Arrays can only contain strings in JSON Config File";
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ }
+ *value = Value(valueStrings);
+ return Status::OK();
+ case ::mongo::Bool:
+ *value = Value(element.Bool());
+ return Status::OK();
+ case ::mongo::EOO:
+ return Status(ErrorCodes::InternalError,
+ "Error converting BSONElement to value; BSONElement empty");
+ default:
+ StringBuilder sb;
+ sb << "Conversion from BSONElement type: " <<
+ element.type() << " not supported.";
+ return Status(ErrorCodes::TypeMismatch, sb.str());
+ }
+ }
+ catch ( std::exception &e ) {
+ StringBuilder sb;
+ sb << "Exception thrown by BSON conversion: " << e.what();
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ }
+
+ // Add all the values in the given BSONObj to our environment. See comments at the
+ // beginning of this section.
+ Status addBSONElementsToEnvironment(const BSONObj& obj,
+ const OptionSection& options,
+ const std::string parentPath,
+ Environment* environment) {
+
+ std::vector<OptionDescription> options_vector;
+ Status ret = options.getAllOptions(&options_vector);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ BSONObjIterator iterator(obj);
+ while (iterator.more()) {
+ BSONElement elem = iterator.next();
+ string fieldName= elem.fieldName();
+
+ std::string dottedName = ( parentPath.empty() ? fieldName
+ : parentPath+'.'+fieldName );
+
+ if (elem.type() == ::mongo::Object) {
+ addBSONElementsToEnvironment( elem.Obj(), options, dottedName, environment );
+ }
+ else {
+ Value optionValue;
+ Status ret = BSONElementToValue(elem, &optionValue);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ ret = isRegistered(options_vector, dottedName);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ Value dummyVal;
+ if (environment->get(dottedName, &dummyVal).isOK()) {
+ StringBuilder sb;
+ sb << "Error parsing JSON config: duplcate key: " << dottedName;
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+
+ ret = environment->set(dottedName, optionValue);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ }
+ }
+
+ return Status::OK();
+ }
+
+ // Iterate through our options and add type constraints to our environment based on what
+ // types the options were registered with. This is needed for the JSON config file
+ // handling, since the JSON parser just reads the types without checking them. Currently,
+ // the boost parsers check the types for us.
+ Status addTypeConstraints(const OptionSection& options, Environment* environment) {
+
+ std::vector<OptionDescription> options_vector;
+ Status ret = options.getAllOptions(&options_vector);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ for(std::vector<OptionDescription>::const_iterator iterator = options_vector.begin();
+ iterator != options_vector.end(); iterator++) {
+ switch (iterator->_type) {
+ case StringVector:
+ environment->addKeyConstraint(
+ new TypeKeyConstraint<std::vector<std::string> >(iterator->_dottedName));
+ break;
+ case Bool:
+ environment->addKeyConstraint(
+ new TypeKeyConstraint<bool>(iterator->_dottedName));
+ break;
+ case Double:
+ environment->addKeyConstraint(
+ new TypeKeyConstraint<double>(iterator->_dottedName));
+ break;
+ case Int:
+ environment->addKeyConstraint(
+ new TypeKeyConstraint<int>(iterator->_dottedName));
+ break;
+ case Long:
+ environment->addKeyConstraint(
+ new TypeKeyConstraint<long>(iterator->_dottedName));
+ break;
+ case String:
+ environment->addKeyConstraint(
+ new TypeKeyConstraint<std::string>(iterator->_dottedName));
+ break;
+ case UnsignedLongLong:
+ environment->addKeyConstraint(
+ new TypeKeyConstraint<unsigned long long>(iterator->_dottedName));
+ break;
+ case Unsigned:
+ environment->addKeyConstraint(
+ new TypeKeyConstraint<unsigned>(iterator->_dottedName));
+ break;
+ case Switch:
+ environment->addKeyConstraint(
+ new TypeKeyConstraint<bool>(iterator->_dottedName));
+ break;
+ default: /* XXX: should not get here */
+ return Status(ErrorCodes::InternalError, "Unrecognized option type");
+ }
+ }
+ return Status::OK();
+ }
+
+ } // namespace
+
+ /**
+ * This function delegates the command line parsing to boost program_options.
+ *
+ * 1. Extract the boost readable option descriptions and positional option descriptions from the
+ * OptionSection
+ * 2. Passes them to the boost command line parser
+ * 3. Copy everything from the variables map returned by boost into the Environment
+ */
+ Status OptionsParser::parseCommandLine(const OptionSection& options,
+ const std::vector<std::string>& argv,
+ Environment* environment) {
+ po::options_description boostOptions;
+ po::positional_options_description boostPositionalOptions;
+ po::variables_map vm;
+
+ // Need to convert to an argc and a vector of char* since that is what
+ // boost::program_options expects as input to its command line parser
+ int argc = 0;
+ std::vector<const char*> argv_buffer;
+ for (std::vector<std::string>::const_iterator iterator = argv.begin();
+ iterator != argv.end(); iterator++) {
+ argv_buffer.push_back(iterator->c_str());
+ argc++;
+ }
+
+ /**
+ * Style options for boost command line parser
+ *
+ * unix_style is an alias for a group of basic style options. We are deviating from that
+ * base style in the following ways:
+ *
+ * 1. Don't allow guessing - '--dbpat' != '--dbpath'
+ * 2. Don't allow sticky - '-hf' != '-h -f'
+ * 3. Allow long disguises - '--dbpath' == '-dbpath'
+ *
+ * In some executables, we are using multiple 'v' options to set the verbosity (e.g. '-vvv')
+ * To make this work, we need to allow long disguises and disallow guessing.
+ */
+ int style = (((po::command_line_style::unix_style ^
+ po::command_line_style::allow_guessing) |
+ po::command_line_style::allow_long_disguise) ^
+ po::command_line_style::allow_sticky);
+
+ Status ret = options.getBoostOptions(&boostOptions);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ ret = options.getBoostPositionalOptions(&boostPositionalOptions);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ try {
+ po::store(po::command_line_parser(argc, &argv_buffer[0]).
+ options(boostOptions).
+ positional(boostPositionalOptions).
+ style(style).
+ run(), vm);
+
+ ret = addBoostVariablesToEnvironment(vm, options, environment);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ }
+ catch (po::error& e) {
+ StringBuilder sb;
+ sb << "Error parsing command line: " << e.what();
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ return Status::OK();
+ }
+
+ /**
+ * This function delegates the INI config parsing to boost program_options.
+ *
+ * 1. Extract the boost readable option descriptions from the OptionSection
+ * 2. Passes them to the boost config file parser
+ * 3. Copy everything from the variables map returned by boost into the Environment
+ */
+ Status OptionsParser::parseINIConfigFile(const OptionSection& options,
+ const std::string& config,
+ Environment* environment) {
+ po::options_description boostOptions;
+ po::variables_map vm;
+
+ Status ret = options.getBoostOptions(&boostOptions);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ std::istringstream is(config);
+ try {
+ po::store(po::parse_config_file(is, boostOptions), vm);
+ ret = addBoostVariablesToEnvironment(vm, options, environment);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ }
+ catch (po::error& e) {
+ StringBuilder sb;
+ sb << "Error parsing INI config file: " << e.what();
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ return Status::OK();
+ }
+
+ /**
+ * This function delegates the JSON config parsing to the MongoDB JSON parser.
+ *
+ * 1. Parse JSON
+ * 2. Add all elements from the resulting BSONObj to the Environment
+ *
+ * This function checks for duplicates and unregistered options, but the caller is responsible
+ * for checking that the options are the correct types
+ *
+ * Also note that the size of our JSON config file is limited in size. The equivalent BSON
+ * object can only be 16MB. We catch the exception that is thrown in this case and return an
+ * error Status from this function
+ */
+ Status OptionsParser::parseJSONConfigFile(const OptionSection& options,
+ const std::string& config,
+ Environment* environment) {
+ BSONObj BSONConfig;
+ try {
+ BSONConfig = fromjson(config);
+ Status ret = addBSONElementsToEnvironment(BSONConfig, options, "", environment);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ } catch ( MsgAssertionException& e ) {
+ StringBuilder sb;
+ sb << "Error parsing JSON config file: " << e.what();
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ return Status::OK();
+ }
+
+ /**
+ * Reads the entire config file into the output string. This is done this way because the JSON
+ * parser only takes complete strings
+ */
+ Status OptionsParser::readConfigFile(const std::string& filename, std::string* contents) {
+
+ FILE* config;
+ config = fopen(filename.c_str(), "r");
+ if (config == NULL) {
+ const int current_errno = errno;
+ StringBuilder sb;
+ sb << "Error reading config file: " << strerror(current_errno);
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+
+ // Get length of config file by seeking to the end and getting the cursor position
+ if (fseek(config, 0L, SEEK_END) != 0) {
+ const int current_errno = errno;
+ // TODO: Make sure errno is the correct way to do this
+ // Confirmed that errno gets set in Mac OSX, but not documented
+ StringBuilder sb;
+ sb << "Error seeking in config file: " << strerror(current_errno);
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ long configSize = ftell(config);
+
+ // Seek back to the beginning of the file for reading
+ if (fseek(config, 0L, SEEK_SET) != 0) {
+ const int current_errno = errno;
+ // TODO: Make sure errno is the correct way to do this
+ // Confirmed that errno gets set in Mac OSX, but not documented
+ StringBuilder sb;
+ sb << "Error seeking in config file: " << strerror(current_errno);
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+
+ // Read into a vector first since it's guaranteed to have contiguous storage
+ std::vector<char> configVector;
+ configVector.resize(configSize);
+ long nread = fread(&configVector[0], sizeof(char), configSize, config);
+ if (nread != configSize) {
+ const int current_errno = errno;
+ // TODO: Make sure errno is the correct way to do this
+ StringBuilder sb;
+ sb << "Error reading in config file: " << strerror(current_errno);
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+
+ // Copy the vector contents into our result string
+ *contents = std::string(configVector.begin(), configVector.end());
+ fclose(config);
+
+ return Status::OK();
+ }
+
+ bool OptionsParser::isJSONConfig(const std::string& config) {
+ for (std::string::const_iterator curChar = config.begin();
+ curChar < config.end(); curChar++) {
+ if (isspace(*curChar)) {
+ // Skip whitespace
+ }
+ else if (*curChar == '{') {
+ // If first non whitespace character is '{', then this is a JSON config file
+ return true;
+ }
+ else {
+ // Otherwise, this is a legacy INI config file
+ return false;
+ }
+ }
+ // Treat the empty config file as INI
+ return false;
+ }
+
+ /**
+ * Run the OptionsParser
+ *
+ * Overview:
+ *
+ * 1. Parse argc and argv using the given OptionSection as a description of expected options
+ * 2. Check for a "config" argument
+ * 3. If "config" found, read config file
+ * 4. Detect config file type (JSON or INI)
+ * 5. Parse config file using the given OptionSection as a description of expected options
+ * 6. Add the results to the output Environment in the proper order to ensure correct precedence
+ */
+ Status OptionsParser::run(const OptionSection& options,
+ const std::vector<std::string>& argv,
+ const std::map<std::string, std::string>& env, // XXX: Currently unused
+ Environment* environment) {
+
+ Environment commandLineEnvironment;
+ Environment configEnvironment;
+
+ Status ret = parseCommandLine(options, argv, &commandLineEnvironment);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ Value config_value;
+ ret = commandLineEnvironment.get(Key("config"), &config_value);
+ // We had an error other than "config" not existing in our environment
+ if (!ret.isOK() && ret != ErrorCodes::KeyNotFound) {
+ return ret;
+ }
+ // "config" exists in our environment
+ else if (ret.isOK()) {
+
+ // Environment::get returns a bad status if config was not set
+ std::string config_filename;
+ ret = config_value.get(&config_filename);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ std::string config_file;
+ ret = readConfigFile(config_filename, &config_file);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ if (isJSONConfig(config_file)) {
+ ret = parseJSONConfigFile(options, config_file, &configEnvironment);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ ret = addTypeConstraints(options, &configEnvironment);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ ret = configEnvironment.validate();
+ if (!ret.isOK()) {
+ return ret;
+ }
+ }
+ else {
+ ret = parseINIConfigFile(options, config_file, &configEnvironment);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ }
+ }
+
+ // Add the values to our result in the order of override
+ // NOTE: This should not fail validation as we haven't called environment->validate() yet
+ ret = environment->setAll(configEnvironment);
+ if (!ret.isOK()) {
+ return ret;
+ }
+ ret = environment->setAll(commandLineEnvironment);
+ if (!ret.isOK()) {
+ return ret;
+ }
+
+ return Status::OK();
+ }
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/options_parser.h b/src/mongo/util/options_parser/options_parser.h
new file mode 100644
index 00000000000..dd401b04f5e
--- /dev/null
+++ b/src/mongo/util/options_parser/options_parser.h
@@ -0,0 +1,115 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 <map>
+#include <string>
+#include <vector>
+
+#include "mongo/base/status.h"
+
+class Status;
+
+namespace mongo {
+namespace optionenvironment {
+
+ class Environment;
+ class OptionSection;
+ class Value;
+
+ /** Handles parsing of the command line as well as JSON and INI config files. Takes an
+ * OptionSection instance that describes the allowed options, parses argv (env not yet
+ * supported), and populates an Environment with the results.
+ *
+ * Usage:
+ *
+ * namespace moe = mongo::optionenvironment;
+ *
+ * moe::OptionsParser parser;
+ * moe::Environment environment;
+ * moe::OptionSection options;
+ *
+ * // Register our allowed options with our OptionSection
+ * options.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display Help"));
+ * options.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+ *
+ * // Run the parser
+ * Status ret = parser.run(options, argv, env, &environment);
+ * if (!ret.isOK()) {
+ * cerr << options.helpString() << endl;
+ * exit(EXIT_FAILURE);
+ * }
+ *
+ * bool displayHelp;
+ * ret = environment.get(moe::Key("help"), &displayHelp);
+ * if (!ret.isOK()) {
+ * // Help is a switch, so it should always be set
+ * cout << "Should not get here" << endl;
+ * exit(EXIT_FAILURE);
+ * }
+ * if (displayHelp) {
+ * cout << options.helpString() << endl;
+ * exit(EXIT_SUCCESS);
+ * }
+ *
+ * // Get the value of port from the environment
+ * int port = 27017;
+ * ret = environment.get(moe::Key("port"), &port);
+ * if (ret.isOK()) {
+ * // We have overridden port here, otherwise it stays as the default.
+ * }
+ */
+ class OptionsParser {
+ public:
+ OptionsParser() { }
+ virtual ~OptionsParser() { }
+
+ /** Handles parsing of the command line as well as JSON and INI config files. The
+ * OptionSection be a description of the allowed options. This function populates the
+ * given Environment with the results of parsing the command line and or config files but
+ * does not call validate on the Environment.
+ *
+ * The only special option is the "config" option. This function will check if the
+ * "config" option was set on the command line and if so attempt to read the given config
+ * file. For binaries that do not support config files, the "config" option should not be
+ * registered in the OptionSection.
+ */
+ Status run(const OptionSection&,
+ const std::vector<std::string>& argv,
+ const std::map<std::string, std::string>& env,
+ Environment*);
+
+ private:
+ /** Handles parsing of the command line and adds the results to the given Environment */
+ Status parseCommandLine(const OptionSection&,
+ const std::vector<std::string>& argv, Environment*);
+
+ /** Handles parsing of an INI config string and adds the results to the given Environment */
+ Status parseINIConfigFile(const OptionSection&, const std::string& config, Environment*);
+
+ /** Handles parsing of a JSON config string and adds the results to the given Environment */
+ Status parseJSONConfigFile(const OptionSection&, const std::string& config, Environment*);
+
+ /** Detects whether the given string represents a JSON config file or an INI config file */
+ bool isJSONConfig(const std::string& config);
+
+ /** Reads the given config file into the output string. This function is virtual for
+ * testing purposes only. */
+ virtual Status readConfigFile(const std::string& filename, std::string*);
+ };
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/options_parser_test.cpp b/src/mongo/util/options_parser/options_parser_test.cpp
new file mode 100644
index 00000000000..d539279aca2
--- /dev/null
+++ b/src/mongo/util/options_parser/options_parser_test.cpp
@@ -0,0 +1,930 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 <map>
+#include <sstream>
+
+#include "mongo/bson/util/builder.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/options_parser/environment.h"
+#include "mongo/util/options_parser/option_description.h"
+#include "mongo/util/options_parser/option_section.h"
+#include "mongo/util/options_parser/options_parser.h"
+
+namespace {
+
+ using mongo::ErrorCodes;
+ using mongo::Status;
+
+ namespace moe = mongo::optionenvironment;
+
+#define TEST_CONFIG_PATH(x) "src/mongo/util/options_parser/test_config_files/" x
+
+ class OptionsParserTester : public moe::OptionsParser {
+ public:
+ Status readConfigFile(const std::string& filename, std::string* config) {
+ if (filename != _filename) {
+ ::mongo::StringBuilder sb;
+ sb << "Parser using filename: " << filename <<
+ " which does not match expected filename: " << _filename;
+ return Status(ErrorCodes::InternalError, sb.str());
+ }
+ *config = _config;
+ return Status::OK();
+ }
+ void setConfig(const std::string& filename, const std::string& config) {
+ _filename = filename;
+ _config = config;
+ }
+ private:
+ std::string _filename;
+ std::string _config;
+ };
+
+ TEST(Registration, DuplicateSingleName) {
+ moe::OptionSection testOpts;
+ ASSERT_OK(testOpts.addOption(moe::OptionDescription("dup", "dup", moe::Switch, "dup")));
+ ASSERT_NOT_OK(testOpts.addOption(moe::OptionDescription("new", "dup", moe::Switch, "dup")));
+ }
+
+ TEST(Registration, DuplicateDottedName) {
+ moe::OptionSection testOpts;
+ ASSERT_OK(testOpts.addOption(moe::OptionDescription("dup", "dup", moe::Switch, "dup")));
+ ASSERT_NOT_OK(testOpts.addOption(moe::OptionDescription("dup", "new", moe::Switch, "dup")));
+ }
+
+ TEST(Registration, DuplicatePositional) {
+ moe::OptionSection testOpts;
+ ASSERT_OK(testOpts.addPositionalOption(moe::PositionalOptionDescription("positional",
+ moe::Int)));
+ ASSERT_NOT_OK(testOpts.addPositionalOption(moe::PositionalOptionDescription("positional",
+ moe::Int)));
+ }
+
+ TEST(Parsing, Good) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--port");
+ argv.push_back("5");
+ argv.push_back("--help");
+ std::map<std::string, std::string> env_map;
+
+ moe::Value value;
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ ASSERT_OK(environment.get(moe::Key("help"), &value));
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 5);
+ }
+
+ TEST(Parsing, SubSection) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ moe::OptionSection subSection("Section Name");
+
+ subSection.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+ testOpts.addSection(subSection);
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--port");
+ argv.push_back("5");
+ std::map<std::string, std::string> env_map;
+
+ moe::Value value;
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 5);
+ }
+
+ TEST(Parsing, StringVector) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("multival", "multival", moe::StringVector,
+ "Multiple Values"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--multival");
+ argv.push_back("val1");
+ argv.push_back("--multival");
+ argv.push_back("val2");
+ std::map<std::string, std::string> env_map;
+
+ moe::Value value;
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ ASSERT_OK(environment.get(moe::Key("multival"), &value));
+ std::vector<std::string> multival;
+ std::vector<std::string>::iterator multivalit;
+ ASSERT_OK(value.get(&multival));
+ multivalit = multival.begin();
+ ASSERT_EQUALS(*multivalit, "val1");
+ multivalit++;
+ ASSERT_EQUALS(*multivalit, "val2");
+ }
+
+ TEST(Parsing, Positional) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", moe::String));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("positional");
+ std::map<std::string, std::string> env_map;
+
+ moe::Value value;
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ ASSERT_OK(environment.get(moe::Key("positional"), &value));
+ std::string positional;
+ ASSERT_OK(value.get(&positional));
+ ASSERT_EQUALS(positional, "positional");
+ }
+
+ TEST(Parsing, PositionalTooMany) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", moe::String));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("positional");
+ argv.push_back("extrapositional");
+ std::map<std::string, std::string> env_map;
+
+ moe::Value value;
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(Parsing, PositionalAndFlag) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addPositionalOption(moe::PositionalOptionDescription("positional", moe::String));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("positional");
+ argv.push_back("--port");
+ argv.push_back("5");
+ std::map<std::string, std::string> env_map;
+
+ moe::Value value;
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ ASSERT_OK(environment.get(moe::Key("positional"), &value));
+ std::string positional;
+ ASSERT_OK(value.get(&positional));
+ ASSERT_EQUALS(positional, "positional");
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 5);
+ }
+
+ TEST(Parsing, PositionalMultiple) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addPositionalOption(moe::PositionalOptionDescription("positional",
+ moe::StringVector, 2));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("positional1");
+ argv.push_back("positional2");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_OK(environment.get(moe::Key("positional"), &value));
+ std::vector<std::string> positional;
+ ASSERT_OK(value.get(&positional));
+ std::vector<std::string>::iterator positionalit = positional.begin();
+ ASSERT_EQUALS(*positionalit, "positional1");
+ positionalit++;
+ ASSERT_EQUALS(*positionalit, "positional2");
+ }
+
+ TEST(Parsing, PositionalMultipleExtra) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addPositionalOption(moe::PositionalOptionDescription("positional",
+ moe::StringVector, 2));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("positional1");
+ argv.push_back("positional2");
+ argv.push_back("positional2");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(Parsing, PositionalMultipleUnlimited) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addPositionalOption(moe::PositionalOptionDescription("positional",
+ moe::StringVector, -1));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("positional1");
+ argv.push_back("positional2");
+ argv.push_back("positional3");
+ argv.push_back("positional4");
+ argv.push_back("positional5");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_OK(environment.get(moe::Key("positional"), &value));
+ std::vector<std::string> positional;
+ ASSERT_OK(value.get(&positional));
+ std::vector<std::string>::iterator positionalit = positional.begin();
+ ASSERT_EQUALS(*positionalit, "positional1");
+ positionalit++;
+ ASSERT_EQUALS(*positionalit, "positional2");
+ positionalit++;
+ ASSERT_EQUALS(*positionalit, "positional3");
+ positionalit++;
+ ASSERT_EQUALS(*positionalit, "positional4");
+ positionalit++;
+ ASSERT_EQUALS(*positionalit, "positional5");
+ }
+
+ TEST(Parsing, PositionalMultipleAndFlag) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addPositionalOption(moe::PositionalOptionDescription("positional",
+ moe::StringVector, 2));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("positional1");
+ argv.push_back("--port");
+ argv.push_back("5");
+ argv.push_back("positional2");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_OK(environment.get(moe::Key("positional"), &value));
+ std::vector<std::string> positional;
+ ASSERT_OK(value.get(&positional));
+ std::vector<std::string>::iterator positionalit = positional.begin();
+ ASSERT_EQUALS(*positionalit, "positional1");
+ positionalit++;
+ ASSERT_EQUALS(*positionalit, "positional2");
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 5);
+ }
+
+ TEST(Parsing, NeedArg) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--port");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(Parsing, BadArg) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--port");
+ argv.push_back("string");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(Parsing, ExtraArg) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--help");
+ argv.push_back("string");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(Style, NoSticky) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("opt", "opt,o", moe::Switch, "first opt"));
+ testOpts.addOption(moe::OptionDescription("arg", "arg,a", moe::Switch, "first arg"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("-oa");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(Style, NoGuessing) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--hel");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(Style, LongDisguises) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("help", "help", moe::Switch, "Display help"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("-help");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_OK(environment.get(moe::Key("help"), &value));
+ bool help;
+ ASSERT_OK(value.get(&help));
+ ASSERT_EQUALS(help, true);
+ }
+
+ TEST(Style, Verbosity) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ ASSERT_OK(testOpts.addOption(moe::OptionDescription("v", "verbose,v", moe::Switch,
+ "be more verbose (include multiple times for more verbosity e.g. -vvvvv)")));
+
+ /* support for -vv -vvvv etc. */
+ for (std::string s = "vv"; s.length() <= 12; s.append("v")) {
+ ASSERT_OK(testOpts.addOption(moe::OptionDescription(s.c_str(), s.c_str(), moe::Switch,
+ "higher verbosity levels (hidden)", false)));
+ }
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("-vvvvvv");
+ std::map<std::string, std::string> env_map;
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+
+ moe::Value value;
+ for (std::string s = "vv"; s.length() <= 12; s.append("v")) {
+ if (s.length() == 6) {
+ ASSERT_OK(environment.get(moe::Key(s), &value));
+ bool verbose;
+ ASSERT_OK(value.get(&verbose));
+ ASSERT_EQUALS(verbose, true);
+ }
+ else {
+ // TODO: Figure out if these are the semantics we want for switches. Right now they
+ // will always set a boolean value in the environment even if they aren't present.
+ ASSERT_OK(environment.get(moe::Key(s), &value));
+ bool verbose;
+ ASSERT_OK(value.get(&verbose));
+ ASSERT_EQUALS(verbose, false);
+ }
+ }
+ }
+
+ TEST(INIConfigFile, Basic) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("default.conf");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("default.conf", "port=5");
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 5);
+ }
+
+ TEST(INIConfigFile, Empty) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("default.conf");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("default.conf", "");
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(INIConfigFile, Override) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("default.conf");
+ argv.push_back("--port");
+ argv.push_back("6");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("default.conf", "port=5");
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 6);
+ }
+
+ TEST(INIConfigFile, Comments) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+ testOpts.addOption(moe::OptionDescription("str", "str", moe::String, "String"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("default.conf");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("default.conf", "# port=5\nstr=NotCommented");
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_NOT_OK(environment.get(moe::Key("port"), &value));
+ ASSERT_OK(environment.get(moe::Key("str"), &value));
+ std::string str;
+ ASSERT_OK(value.get(&str));
+ ASSERT_EQUALS(str, "NotCommented");
+ }
+
+ TEST(JSONConfigFile, Basic) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", "{ port : 5 }");
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 5);
+ }
+
+ TEST(JSONConfigFile, Empty) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", "");
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(JSONConfigFile, EmptyObject) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", "{}");
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(JSONConfigFile, Override) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ argv.push_back("--port");
+ argv.push_back("6");
+ std::map<std::string, std::string> env_map;
+
+
+ parser.setConfig("config.json", "{ port : 5 }");
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 6);
+ }
+
+ TEST(JSONConfigFile, UnregisteredOption) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", "{ port : 5 }");
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(JSONConfigFile, DuplicateOption) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", "{ port : 5, port : 5 }");
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(JSONConfigFile, BadType) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", "{ port : \"string\" }");
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(JSONConfigFile, Nested) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("nested.port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", "{ nested : { port : 5 } }");
+
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ moe::Value value;
+ ASSERT_OK(environment.get(moe::Key("nested.port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 5);
+ }
+
+ TEST(JSONConfigFile, StringVector) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("multival", "multival", moe::StringVector,
+ "Multiple Values"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", "{ multival : [ \"val1\", \"val2\" ] }");
+
+ moe::Value value;
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ ASSERT_OK(environment.get(moe::Key("multival"), &value));
+ std::vector<std::string> multival;
+ std::vector<std::string>::iterator multivalit;
+ ASSERT_OK(value.get(&multival));
+ multivalit = multival.begin();
+ ASSERT_EQUALS(*multivalit, "val1");
+ multivalit++;
+ ASSERT_EQUALS(*multivalit, "val2");
+ }
+
+ TEST(JSONConfigFile, StringVectorNonString) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("multival", "multival", moe::StringVector,
+ "Multiple Values"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("config.json", "{ multival : [ 1 ] }");
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(JSONConfigFile, Over16Megabytes) {
+ // Test to make sure that we fail gracefully when we try to parse a JSON config file that
+ // results in a BSON object larger than the current limit of 16MB
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("config.json");
+ std::map<std::string, std::string> env_map;
+
+ // 1024 characters = 64 * 16
+ const std::string largeString =
+ "\""
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ "\"";
+
+ std::string largeConfigString;
+
+ largeConfigString.append("{ \"largeArray\" : [ ");
+
+ // 16mb = 16 * 1024kb = 16384
+ for (int i = 0; i < 16383; i++) {
+ largeConfigString.append(largeString);
+ largeConfigString.append(",");
+ }
+ largeConfigString.append(largeString);
+ largeConfigString.append(" ] }");
+
+ parser.setConfig("config.json", largeConfigString);
+
+ moe::Value value;
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(Parsing, BadConfigFileOption) {
+ OptionsParserTester parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+
+ // TODO: Should the error be in here?
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::Int, "Config file to parse"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back("1");
+ std::map<std::string, std::string> env_map;
+
+ parser.setConfig("default.conf", "");
+
+ ASSERT_NOT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+ TEST(ConfigFromFilesystem, JSONGood) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back(TEST_CONFIG_PATH("good.json"));
+ std::map<std::string, std::string> env_map;
+
+ moe::Value value;
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 5);
+ }
+
+ TEST(ConfigFromFilesystem, INIGood) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+ testOpts.addOption(moe::OptionDescription("port", "port", moe::Int, "Port"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back(TEST_CONFIG_PATH("good.conf"));
+ std::map<std::string, std::string> env_map;
+
+ moe::Value value;
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ ASSERT_OK(environment.get(moe::Key("port"), &value));
+ int port;
+ ASSERT_OK(value.get(&port));
+ ASSERT_EQUALS(port, 5);
+ }
+
+ TEST(ConfigFromFilesystem, Empty) {
+ moe::OptionsParser parser;
+ moe::Environment environment;
+
+ moe::OptionSection testOpts;
+ testOpts.addOption(moe::OptionDescription("config", "config",
+ moe::String, "Config file to parse"));
+
+ std::vector<std::string> argv;
+ argv.push_back("binaryname");
+ argv.push_back("--config");
+ argv.push_back(TEST_CONFIG_PATH("empty.json"));
+ std::map<std::string, std::string> env_map;
+
+ moe::Value value;
+ ASSERT_OK(parser.run(testOpts, argv, env_map, &environment));
+ }
+
+} // unnamed namespace
diff --git a/src/mongo/util/options_parser/test_config_files/empty.json b/src/mongo/util/options_parser/test_config_files/empty.json
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/src/mongo/util/options_parser/test_config_files/empty.json
diff --git a/src/mongo/util/options_parser/test_config_files/good.conf b/src/mongo/util/options_parser/test_config_files/good.conf
new file mode 100644
index 00000000000..75bcd785281
--- /dev/null
+++ b/src/mongo/util/options_parser/test_config_files/good.conf
@@ -0,0 +1 @@
+port=5
diff --git a/src/mongo/util/options_parser/test_config_files/good.json b/src/mongo/util/options_parser/test_config_files/good.json
new file mode 100644
index 00000000000..83ba69eef58
--- /dev/null
+++ b/src/mongo/util/options_parser/test_config_files/good.json
@@ -0,0 +1,3 @@
+{
+ "port" : 5
+}
diff --git a/src/mongo/util/options_parser/value.cpp b/src/mongo/util/options_parser/value.cpp
new file mode 100644
index 00000000000..89ad7388a41
--- /dev/null
+++ b/src/mongo/util/options_parser/value.cpp
@@ -0,0 +1,158 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 "mongo/util/options_parser/value.h"
+
+#include "mongo/bson/util/builder.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ // Value implementation
+
+ // Value access functions
+
+ Status Value::get(std::vector<std::string>* val) const {
+ if (type != StringVector) {
+ return Status(ErrorCodes::TypeMismatch, "Value not of type: stringVector");
+ }
+ *val = stringVectorVal;
+ return Status::OK();
+ }
+ Status Value::get(bool* val) const {
+ if (type != Bool) {
+ return Status(ErrorCodes::TypeMismatch, "Value not of type: bool");
+ }
+ *val = boolVal;
+ return Status::OK();
+ }
+ Status Value::get(double* val) const {
+ if (type != Double) {
+ return Status(ErrorCodes::TypeMismatch, "Value not of type: double");
+ }
+ *val = doubleVal;
+ return Status::OK();
+ }
+ Status Value::get(int* val) const {
+ if (type != Int) {
+ return Status(ErrorCodes::TypeMismatch, "Value not of type: int");
+ }
+ *val = intVal;
+ return Status::OK();
+ }
+ Status Value::get(long* val) const {
+ if (type == Long) {
+ *val = longVal;
+ return Status::OK();
+ }
+ else if (type == Int) {
+ *val = intVal;
+ return Status::OK();
+ }
+ return Status(ErrorCodes::TypeMismatch, "Value not convertible to type: long");
+ }
+ Status Value::get(std::string* val) const {
+ if (type != String) {
+ return Status(ErrorCodes::TypeMismatch, "Value not of type: string");
+ }
+ *val = stringVal;
+ return Status::OK();
+ }
+ Status Value::get(unsigned long long* val) const {
+ if (type == UnsignedLongLong) {
+ *val = unsignedLongLongVal;
+ return Status::OK();
+ }
+ else if (type == Unsigned) {
+ *val = unsignedVal;
+ return Status::OK();
+ }
+ return Status(ErrorCodes::TypeMismatch, "Value not convertible to type: unsignedlonglong");
+ }
+ Status Value::get(unsigned* val) const {
+ if (type != Unsigned) {
+ return Status(ErrorCodes::TypeMismatch, "Value not of type: unsigned");
+ }
+ *val = unsignedVal;
+ return Status::OK();
+ }
+
+ // Value utility functions
+
+ std::string Value::typeToString() const {
+ switch (type) {
+ case StringVector: return "StringVector";
+ case Bool: return "Bool";
+ case Double: return "Double";
+ case Int: return "Int";
+ case Long: return "Long";
+ case String: return "String";
+ case UnsignedLongLong: return "UnsignedLongLong";
+ case Unsigned: return "Unsigned";
+ case None: return "None";
+ default: return "Unknown";
+ }
+ }
+ bool Value::isEmpty() const {
+ return type == None;
+ }
+ bool Value::equal(Value& otherVal) const {
+ if (type != otherVal.type) {
+ return false;
+ }
+ switch (type) {
+ case StringVector: return stringVectorVal == otherVal.stringVectorVal;
+ case Bool: return boolVal == otherVal.boolVal;
+ case Double: return doubleVal == otherVal.doubleVal;
+ case Int: return intVal == otherVal.intVal;
+ case Long: return longVal == otherVal.longVal;
+ case String: return stringVal == otherVal.stringVal;
+ case UnsignedLongLong: return unsignedLongLongVal == otherVal.unsignedLongLongVal;
+ case Unsigned: return unsignedVal == otherVal.unsignedVal;
+ case None: return true;
+ default: return false; /* Undefined */
+ }
+ }
+ std::string Value::toString() const {
+ StringBuilder sb;
+ switch (type) {
+ case StringVector:
+ if (!stringVectorVal.empty())
+ {
+ // Convert all but the last element to avoid a trailing ","
+ for(std::vector<std::string>::const_iterator iterator = stringVectorVal.begin();
+ iterator != stringVectorVal.end() - 1; iterator++) {
+ sb << *iterator;
+ }
+
+ // Now add the last element with no delimiter
+ sb << stringVectorVal.back();
+ }
+ break;
+ case Bool: sb << boolVal; break;
+ case Double: sb << doubleVal; break;
+ case Int: sb << intVal; break;
+ case Long: sb << longVal; break;
+ case String: sb << stringVal; break;
+ case UnsignedLongLong: sb << unsignedLongLongVal; break;
+ case Unsigned: sb << unsignedVal; break;
+ case None: sb << "(not set)"; break;
+ default: sb << "(undefined)"; break;
+ }
+ return sb.str();
+ }
+
+} // namespace optionenvironment
+} // namespace mongo
diff --git a/src/mongo/util/options_parser/value.h b/src/mongo/util/options_parser/value.h
new file mode 100644
index 00000000000..5b0ab751e9a
--- /dev/null
+++ b/src/mongo/util/options_parser/value.h
@@ -0,0 +1,125 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * 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 <vector>
+
+#include "mongo/base/status.h"
+
+namespace mongo {
+namespace optionenvironment {
+
+ class Constraint;
+ class KeyConstraint;
+
+ typedef std::string Key;
+
+ /** A simple container interface for storing various C++ values.
+ *
+ * Usage:
+ *
+ * Value intVal(2);
+ * Value stringVal("string");
+ *
+ * int intContents = 1;
+ * Status ret = stringVal.get(&intContents);
+ * // ret != Status::OK()
+ * // intContents is still 1
+ *
+ * ret = intVal.get(&intContents);
+ * // ret == Status::OK()
+ * // intContents is now 2
+ */
+ class Value {
+ public:
+
+ // Constructors
+
+ explicit Value() : type(None) { }
+ explicit Value(std::vector<std::string> val) : stringVectorVal(val), type(StringVector) { }
+ explicit Value(bool val) : boolVal(val), type(Bool) { }
+ explicit Value(double val) : doubleVal(val), type(Double) { }
+ explicit Value(int val) : intVal(val), type(Int) { }
+ explicit Value(long val) : longVal(val), type(Long) { }
+ explicit Value(std::string val) : stringVal(val), type(String) { }
+ explicit Value(unsigned long long val) : unsignedLongLongVal(val), type(UnsignedLongLong) {}
+ explicit Value(unsigned val) : unsignedVal(val), type(Unsigned) { }
+
+ // Access interface
+
+ Status get(std::vector<std::string>* val) const;
+ Status get(bool* val) const;
+ Status get(double* val) const;
+ Status get(int* val) const;
+ Status get(long* val) const;
+ Status get(string* val) const;
+ Status get(unsigned long long* val) const;
+ Status get(unsigned* val) const;
+
+ // Utility functions
+
+ /**
+ * Return the value's type as a string
+ */
+ std::string typeToString() const;
+
+ /**
+ * Return true if the value was created with the no argument constructor
+ */
+ bool isEmpty() const;
+
+ /**
+ * Return true if the other Value equals this value, both in type and in contents
+ *
+ * Two empty values are equal
+ */
+ bool equal(Value&) const;
+
+ /**
+ * Return the string representation of this Value
+ */
+ std::string toString() const;
+
+ private:
+ std::vector<std::string> stringVectorVal;
+ std::string stringVal;
+ union {
+ bool boolVal;
+ double doubleVal;
+ int intVal;
+ long longVal;
+ unsigned long long unsignedLongLongVal;
+ unsigned unsignedVal;
+ };
+
+ // Types currently supported by Value
+ enum Type {
+ StringVector, // std::vector<std::string>
+ Bool, // bool
+ Double, // double
+ Int, // int
+ Long, // long
+ String, // std::string
+ UnsignedLongLong, // unsigned long long
+ Unsigned, // unsigned
+ None, // (not set)
+ };
+
+ Type type;
+ };
+
+} // namespace optionenvironment
+} // namespace mongo