infoedit

Change-Id: I4d08ee17075f0b0e205817be7dcdb417073df43a
refs: #2964
diff --git a/install_helpers/infoedit/infoedit.cpp b/install_helpers/infoedit/infoedit.cpp
new file mode 100644
index 0000000..958b54f
--- /dev/null
+++ b/install_helpers/infoedit/infoedit.cpp
@@ -0,0 +1,186 @@
+/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
+/*
+ * Copyright 2015, Regents of the University of California.
+ *
+ * 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 "infoedit.hpp"
+#include <iostream>
+#include <boost/program_options/options_description.hpp>
+#include <boost/program_options/variables_map.hpp>
+#include <boost/program_options/parsers.hpp>
+
+namespace infoedit {
+
+void
+InfoEditor::load(const std::string& fileName)
+{
+  std::ifstream input(fileName);
+  if (!input.good() || !input.is_open()) {
+    throw Error("Failed to open configuration file for parsing");
+  }
+
+  try {
+    boost::property_tree::info_parser::read_info(input, m_info);
+  }
+  catch (boost::property_tree::info_parser::info_parser_error& error) {
+    std::stringstream msg;
+    msg << "Failed to parse configuration file";
+    msg << " " << error.message() << " line " << error.line();
+    throw Error(msg.str());
+  }
+
+  input.close();
+}
+
+InfoEditor&
+InfoEditor::modify(const std::string& section, const std::string& value)
+{
+  m_info.put(section.c_str(), value.c_str());
+  return *this;
+}
+
+InfoEditor&
+InfoEditor::remove(const std::string& section)
+{
+  std::size_t pos = section.find_last_of(".");
+  if (pos == std::string::npos) {
+    m_info.erase(section.c_str());
+  }
+  else {
+    boost::optional<boost::property_tree::ptree&> child =
+      m_info.get_child_optional(section.substr(0, pos));
+    if (child) {
+      child->erase(section.substr(pos + 1));
+    }
+  }
+
+  return *this;
+}
+
+InfoEditor&
+InfoEditor::insert(const std::string& section, std::istream& stream)
+{
+  boost::property_tree::ptree pt;
+  read_info(stream, pt);
+
+  m_info.add_child(section.c_str(), pt);
+
+  return *this;
+}
+
+void
+InfoEditor::save(const std::string& fileName)
+{
+  std::ofstream output(fileName);
+  write_info(output, m_info);
+  output.close();
+}
+
+int
+main(int argc, char** argv)
+{
+  std::string configFile;
+  std::string sectionPath;
+  std::string value;
+
+  namespace po = boost::program_options;
+  po::options_description description("Usage\n"
+                                      "  infoedit [-f file] [-s|d|a|r path] [-v value]\n"
+                                      "Options");
+
+  description.add_options()
+    ("help,h",    "print this help message")
+    ("file,f",    po::value<std::string>(&configFile), "the file to edit")
+    ("section,s", po::value<std::string>(&sectionPath), "the section to modify")
+    ("value,v",   po::value<std::string>(&value), "the value used to modify some section")
+    ("delete,d",  po::value<std::string>(&sectionPath), "the sub tree to delete")
+    ("add,a",     po::value<std::string>(&sectionPath), "adds a sub tree")
+    ("replace,r", po::value<std::string>(&sectionPath), "replace the sub tree")
+    ;
+
+  po::variables_map vm;
+  try {
+      po::store(po::command_line_parser(argc, argv).options(description).run(), vm);
+      po::notify(vm);
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what()
+              << "\n"
+              << description;
+    return 1;
+  }
+
+  if (vm.count("help") > 0) {
+    std::cout << description;
+    return 0;
+  }
+
+  if (vm.count("file") == 0) {
+    std::cerr << "ERROR: the file to edit should be specified"
+              << "\n"
+              << description;
+    return 1;
+  }
+
+  InfoEditor editor;
+  try {
+    editor.load(configFile);
+  }
+  catch (const std::exception& e) {
+    std::cerr << "ERROR: " << e.what() << std::endl;
+    return 1;
+  }
+
+  try {
+    if (vm.count("section") > 0) {
+      if (vm.count("value") == 0) {
+        std::cerr << "ERROR: value must be specified" << std::endl;
+        return 1;
+      }
+
+      editor.modify(sectionPath, value);
+    }
+
+    if (vm.count("delete") > 0) {
+      editor.remove(sectionPath);
+    }
+
+    if (vm.count("add") > 0) {
+      editor.insert(sectionPath, std::cin);
+    }
+
+    if (vm.count("replace") > 0) {
+      editor.remove(sectionPath).insert(sectionPath, std::cin);
+    }
+  }
+  catch (...) {
+    return 1;
+  }
+
+  try {
+    editor.save(configFile);
+  }
+  catch (...) {
+    return 1;
+  }
+
+  return 0;
+}
+
+} // namespace infoedit
+
+int
+main(int argc, char** argv)
+{
+  return infoedit::main(argc, argv);
+}