config_parser: Add parser warnings

This makes it possible to determine if the file had formatting issues
and should be rewritten.

Change-Id: I1a1fd683b5733bff1841703dcd7be99688b66c54
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/config_parser.cpp b/src/config_parser.cpp
index 26226d4..1d8371d 100644
--- a/src/config_parser.cpp
+++ b/src/config_parser.cpp
@@ -1,5 +1,7 @@
 #include "config_parser.hpp"
 
+#include <functional>
+#include <iterator>
 #include <stdplus/exception.hpp>
 #include <stdplus/fd/create.hpp>
 #include <stdplus/fd/line.hpp>
@@ -61,12 +63,38 @@
 
 struct Parse
 {
+    std::reference_wrapper<const fs::path> filename;
     SectionMap sections;
-    KeyValuesMap* section = nullptr;
+    KeyValuesMap* section;
+    std::vector<std::string> warnings;
+    size_t lineno;
+
+    inline Parse(const fs::path& filename) :
+        filename(filename), section(nullptr), lineno(0)
+    {
+    }
 
     void pumpSection(std::string_view line)
     {
         auto cpos = line.find(']');
+        if (cpos == line.npos)
+        {
+            warnings.emplace_back(fmt::format("{}:{}: Section missing ]",
+                                              filename.get().native(), lineno));
+        }
+        else
+        {
+            for (auto c : line.substr(cpos + 1))
+            {
+                if (!isspace(c))
+                {
+                    warnings.emplace_back(
+                        fmt::format("{}:{}: Characters outside section name",
+                                    filename.get().native(), lineno));
+                    break;
+                }
+            }
+        }
         auto s = line.substr(0, cpos);
         auto it = sections.find(s);
         if (it == sections.end())
@@ -80,16 +108,27 @@
     void pumpKV(std::string_view line)
     {
         auto epos = line.find('=');
+        std::vector<std::string> new_warnings;
         if (epos == line.npos)
         {
-            return;
-        }
-        if (section == nullptr)
-        {
-            return;
+            new_warnings.emplace_back(fmt::format(
+                "{}:{}: KV missing `=`", filename.get().native(), lineno));
         }
         auto k = line.substr(0, epos);
         removePadding(k);
+        if (section == nullptr)
+        {
+            new_warnings.emplace_back(
+                fmt::format("{}:{}: Key `{}` missing section",
+                            filename.get().native(), lineno, k));
+        }
+        if (!new_warnings.empty())
+        {
+            warnings.insert(warnings.end(),
+                            std::make_move_iterator(new_warnings.begin()),
+                            std::make_move_iterator(new_warnings.end()));
+            return;
+        }
         auto v = line.substr(epos + 1);
         removePadding(v);
 
@@ -103,6 +142,7 @@
 
     void pump(std::string_view line)
     {
+        lineno++;
         for (size_t i = 0; i < line.size(); ++i)
         {
             auto c = line[i];
@@ -124,7 +164,7 @@
 
 void Parser::setFile(const fs::path& filename)
 {
-    Parse parse;
+    Parse parse(filename);
 
     try
     {
@@ -136,12 +176,18 @@
             parse.pump(*reader.readLine());
         }
     }
-    catch (...)
+    catch (const stdplus::exception::Eof&)
+    {
+    }
+    catch (const std::exception& e)
     {
         // TODO: Pass exceptions once callers can handle them
+        parse.warnings.emplace_back(
+            fmt::format("{}: Read error: {}", filename.native(), e.what()));
     }
 
     this->sections = std::move(parse.sections);
+    this->warnings = std::move(parse.warnings);
 }
 
 } // namespace config
diff --git a/src/config_parser.hpp b/src/config_parser.hpp
index 9a30833..abbf25c 100644
--- a/src/config_parser.hpp
+++ b/src/config_parser.hpp
@@ -48,6 +48,14 @@
     const ValueList& getValues(std::string_view section,
                                std::string_view key) const noexcept;
 
+    /** @brief Determine if there were warnings parsing the file
+     *  @return The number of parsing issues in the file
+     */
+    inline const std::vector<std::string>& getWarnings() const noexcept
+    {
+        return warnings;
+    }
+
     /** @brief Set the file name and parse it.
      *  @param[in] filename - Absolute path of the file.
      */
@@ -55,6 +63,7 @@
 
   private:
     SectionMap sections;
+    std::vector<std::string> warnings;
 };
 
 } // namespace config