diff --git a/config/online_monitor.conf b/config/online_monitor.conf
new file mode 100644
index 0000000000000000000000000000000000000000..da29f68cc146abcbb4a0d24e82c23fbf880b1983
--- /dev/null
+++ b/config/online_monitor.conf
@@ -0,0 +1,10 @@
+# configuration for online monitoring
+
+host = clrlpc.jlab.org
+port = 11111
+etfile = /tmp/et_test
+modules = ${THIS_DIR}/esb_module.conf
+output = ${THIS_DIR}/../processed_data/online.root
+update_interval = 2000  # ms
+
+
diff --git a/include/utils.h b/include/utils.h
index 7f8fbfad76719d416a5065a7c24d4938320debe7..1d3f4d27e1d2520f4935dfd8bbeb3d3495c97ae3 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -114,9 +114,9 @@ std::vector<Module> read_modules(const std::string &path, bool verbose=false)
 
         // module attributes
         Module mod;
-        mod.crate = conf.GetConfigValue("crate").Int();
-        mod.slot = conf.GetConfigValue("slot").Int();
-        mod.type = str2ModuleType(conf.GetConfigValue("type").c_str());
+        mod.crate = conf.Value("crate").Int();
+        mod.slot = conf.Value("slot").Int();
+        mod.type = str2ModuleType(conf.Value("type").c_str());
 
         // channels
         for (auto &sub : btext.blocks) {
diff --git a/online/monitor.cxx b/online/monitor.cxx
index 46af8c3c319636c18e962d713dc629c244573510..8a4c42f2d0a1aecbbd3342d6c7d3e0763017b882 100644
--- a/online/monitor.cxx
+++ b/online/monitor.cxx
@@ -17,7 +17,6 @@
 
 
 #define FADC_BANK 3
-#define PROGRESS_COUNT 100
 
 
 volatile sig_atomic_t term = 0;
@@ -309,19 +308,29 @@ void processEvent(const uint32_t *buf, int &count, TTree *tree, bool &init_tree,
     }
 }
 
-void monitor(const std::string &addr = "clrlpc", int port = 11111, const std::string &etfile = "/tmp/et_test",
-             const std::string &opath = "processed_data/online.root",
-             const std::string &module_conf = "config/esb_module.conf",
-             int nev = -1)
+void monitor(const std::string &cpath = "config/online_monitor.conf")
 {
-    auto modules = read_modules(module_conf);
+    // read configuration
+    ConfigObject conf;
+    conf.ReadConfigFile(cpath);
+    auto host = conf.Value<std::string>("host", "clrlpc.jlab.org");
+    auto port = conf.Value<int>("port", 11111);
+    auto etfile = conf.Value<std::string>("etfile", "/tmp/et_test");
+    auto modconf = conf.Value<std::string>("modules", "config/esb_module.conf");
+    auto up_intvl = conf.Value<double>("update_interval", 5000);
+    auto outfile = conf.Value<std::string>("output", "processed_data/online.root");
+    auto nev = conf.Value<int>("number_events", -1);
+
+    // connect et
     ETChannel et_chan(100, 100);
-    if (et_chan.Connect(addr.c_str(), port, etfile.c_str(), "TCD_MONITOR") != ET_OK) {
+    if (et_chan.Connect(host.c_str(), port, etfile.c_str(), "TCD_MONITOR") != ET_OK) {
+        std::cout << fmt::format("Failed to connect to the et system {}:{}:{}, abort monitoring!", host, port, etfile)
+                  << std::endl;
         return;
     }
+    et_chan.Open();
 
-    auto *hfile = new TFile(opath.c_str(), "RECREATE", "MAPMT test results");
-    auto tree = new TTree("EvTree", "Cherenkov Test Events");
+    auto modules = read_modules(modconf);
     // set up branches and a container to be combined with the branches
     std::unordered_map<std::string, BranchData> brdata;
     for (auto &mod : modules) {
@@ -334,25 +343,34 @@ void monitor(const std::string &addr = "clrlpc", int port = 11111, const std::st
         }
     }
 
+    // root file and event tree
+    auto *hfile = new TFile(outfile.c_str(), "RECREATE", "MAPMT test results");
+    auto tree = new TTree("EvTree", "Cherenkov Test Events");
+
+    // display plots
     auto dply = CreateMonitorDisplay("/online/latest/", 9100, tree);
     dply->InitAll();
 
+
+    // timer
+    auto timer = std::chrono::steady_clock::now();
+
+    // start monitoring
     int count = 0;
-    bool init_tree = false;
     int data_mode = -1;
-    int status = ETChannel::READ_EMPTY;
-    et_chan.Open();
-    signal (SIGINT, handle_sig);
-
+    bool init_tree = false;
+    signal(SIGINT, handle_sig);
     while (true) {
         if (term > 0 || (nev > 0 && nev < count)) { break; }
-        if (count % PROGRESS_COUNT == 0 && count > 0) {
-            std::cout << "Read and processed events - " << count << std::endl;
+        auto now = std::chrono::steady_clock::now();
+        if (std::chrono::duration_cast<std::chrono::milliseconds>(now - timer).count() > up_intvl) {
+            timer = now;
+            std::cout << "Monitor update, processed events - " << count << std::endl;
             dply->Process();
             dply->UpdateAll();
         }
 
-        status = et_chan.ReadEvent();
+        int status = et_chan.ReadEvent();
 
         switch (status) {
         case ETChannel::READ_ERROR:
diff --git a/rootlogon.C b/rootlogon.C
new file mode 100644
index 0000000000000000000000000000000000000000..fbc218253cd3703d407784214e490c976ee6a209
--- /dev/null
+++ b/rootlogon.C
@@ -0,0 +1,10 @@
+{
+    gInterpreter->AddIncludePath("./include");
+    gInterpreter->AddIncludePath("./third_party/et_coda_3.10/include");
+    gInterpreter->AddIncludePath("./third_party/simple");
+    gInterpreter->AddIncludePath("./third_party/prconf/include");
+    // gSystem->Load("libcoda_et.so");
+    gSystem->Load("./third_party/et_coda_3.10/lib/libet.so");
+    gSystem->Load("./third_party/prconf/libprconf.so");
+    gSystem->Load("./third_party/simple/libsimple.so");
+}
diff --git a/src/esb_analyze_online.cpp b/src/esb_analyze_online.cpp
index 9cafff5d092cdcf907b4c9c74102e416b33a32d0..f7a896264a78ac536f2c11e468ec183ce417df22 100644
--- a/src/esb_analyze_online.cpp
+++ b/src/esb_analyze_online.cpp
@@ -6,10 +6,6 @@
 #include "ConfigParser.h"
 #include "ConfigObject.h"
 #include "ConfigOption.h"
-#include "THaCodaFile.h"
-#include "THaEvData.h"
-#include "CodaDecoder.h"
-#include "evio.h"
 #include "TSpectrum.h"
 #include "TTree.h"
 #include "TFile.h"
@@ -23,28 +19,17 @@
 #define PROGRESS_COUNT 100
 
 
-volatile sig_atomic_t sig_caught = 0;
+volatile sig_atomic_t terminate = 0;
 
 void handle_sig(int signum)
 {
-    /* in case we registered this handler for multiple signals */
-    if (signum == SIGINT) {
-        sig_caught = 1;
-    }
-    if (signum == SIGTERM) {
-        sig_caught = 2;
-    }
-    if (signum == SIGABRT) {
-        sig_caught = 3;
-    }
+    terminate = 1;
 }
 
 
 
-void write_raw_data(const std::string &addr, int port, const std::string &etfile,
-                    const std::string &opath, int nev, const std::vector<Module> &modules);
-bool parseEvent(const uint32_t *buf, bool verbose);
-uint32_t parseBlock(const uint32_t *buf);
+void monitor(const std::string &addr, int port, const std::string &etfile,
+             const std::string &opath, const std::string &modules_conf, int nev);
 
 
 uint32_t swap_endian32(uint32_t num)
@@ -56,6 +41,21 @@ uint32_t swap_endian32(uint32_t num)
     return b0 | b1 | b2 | b3;
 }
 
+
+uint32_t parseBlock(const uint32_t *buf)
+{
+    auto header = (ETChannel::CodaEvHeader*) buf;
+    const uint32_t buf_size = header->length + 1;
+    std::cout << "ROC header: " << std::dec << buf_size << "\n"
+              << (int) header->etype << ", " << (int) header->dtype << ", " << (int) header->num
+              << std::endl;
+    std::cout << std::hex;
+    for (uint32_t i = 0; i < buf_size; ++i) {
+        std::cout << "0x" << std::setw(8) << std::setfill('0') << buf[i] << "\n";
+    }
+    return buf_size;
+}
+
 // parse an evio event
 bool parseEvent(const uint32_t *buf, bool verbose = false)
 {
@@ -99,21 +99,6 @@ bool parseEvent(const uint32_t *buf, bool verbose = false)
 }
 
 
-uint32_t parseBlock(const uint32_t *buf)
-{
-    auto header = (ETChannel::CodaEvHeader*) buf;
-    const uint32_t buf_size = header->length + 1;
-    std::cout << "ROC header: " << std::dec << buf_size << "\n"
-              << (int) header->etype << ", " << (int) header->dtype << ", " << (int) header->num
-              << std::endl;
-    std::cout << std::hex;
-    for (uint32_t i = 0; i < buf_size; ++i) {
-        std::cout << "0x" << std::setw(8) << std::setfill('0') << buf[i] << "\n";
-    }
-    return buf_size;
-}
-
-
 int main(int argc, char* argv[])
 {
     // setup input arguments
@@ -166,9 +151,7 @@ int main(int argc, char* argv[])
         }
     }
 
-    auto modules = read_modules(mconf);
-
-    write_raw_data(host, port, etfile, conf_opt.GetArgument(0), nev, modules);
+    monitor(host, port, etfile, conf_opt.GetArgument(0), mconf, nev);
 }
 
 
@@ -339,7 +322,7 @@ void fill_tree(TTree *tree, std::unordered_map<std::string, BranchData> &brdata,
 void processEvent(const uint32_t *buf, int &count, TTree *tree, bool &init_tree, int &data_mode,
                   const std::vector<Module> &modules, std::unordered_map<std::string, BranchData> &brdata)
 {
-    if (!parseEvent(buf)) {
+    if (!parseEvent(buf, false)) {
         return;
     }
 
@@ -388,15 +371,17 @@ void processEvent(const uint32_t *buf, int &count, TTree *tree, bool &init_tree,
 }
 
 
-void write_raw_data(const std::string &addr, int port, const std::string &etfile,
-                    const std::string &opath, int nev, const std::vector<Module> &modules)
+void monitor(const std::string &addr, int port, const std::string &etfile,
+             const std::string &opath, const std::string &mconf, int nev)
 {
     ETChannel et_chan(100, 100);
     if (et_chan.Connect(addr.c_str(), port, etfile.c_str(), "TCD_MONITOR") != ET_OK) {
         return;
     }
 
-    auto *hfile = new TFile(opath.c_str(), "RECREATE", "MAPMT test results");
+    auto modules = read_modules(mconf);
+
+    auto *hfile = new TFile(opath.c_str(), "RECREATE", "MAPMT online monitor");
     auto tree = new TTree("EvTree", "Cherenkov Test Events");
     // set up branches and a container to be combined with the branches
     std::unordered_map<std::string, BranchData> brdata;
@@ -413,26 +398,33 @@ void write_raw_data(const std::string &addr, int port, const std::string &etfile
     int count = 0;
     bool init_tree = false;
     int data_mode = -1;
+    int status = ETChannel::READ_EMPTY;
     et_chan.Open();
-    int status = et_chan.ReadEvent();
     signal (SIGINT, handle_sig);
-    while (status != ETChannel::READ_ERROR && status != ETChannel::READ_EOF) {
-        if (sig_caught || (nev > 0 && nev < count)) { break; }
-        // read-in data
-        if (status == ETChannel::READ_EMPTY) {
-            std::cout << "ET station is empty, wating 5 secs..." << std::endl;
-            std::this_thread::sleep_for(std::chrono::seconds(5));
-            status = et_chan.ReadEvent();
-            continue;
-        }
 
-        if((count % PROGRESS_COUNT) == 0) {
+    while (true) {
+        if (terminate > 0 || (nev > 0 && nev < count)) { break; }
+        if (count % PROGRESS_COUNT == 0) {
             std::cout << "Read and processed events - " << count << std::endl;
         }
 
-        processEvent(et_chan.GetEvBuffer(), count, tree, init_tree, data_mode, modules, brdata);
         status = et_chan.ReadEvent();
-    }
+
+        switch (status) {
+        case ETChannel::READ_ERROR:
+        case ETChannel::READ_EOF:
+            terminate = 1;
+            break;
+        case ETChannel::READ_EMPTY:
+            std::cout << "ET station is empty, wait 2 secs..." << std::endl;
+            std::this_thread::sleep_for(std::chrono::seconds(2));
+            break;
+        case ETChannel::READ_OK:
+            processEvent(et_chan.GetEvBuffer(), count, tree, init_tree, data_mode, modules, brdata);
+            break;
+        }
+    }       
+
     std::cout << "Read and processed events - " << count << std::endl;
 
     hfile->Write();
diff --git a/third_party/prconf/include/ConfigObject.h b/third_party/prconf/include/ConfigObject.h
index aa3bd1c82e8582cd994dca214d910b6edaf9076c..757f32eda3d1029ecdb142e57b91051938fd8814 100644
--- a/third_party/prconf/include/ConfigObject.h
+++ b/third_party/prconf/include/ConfigObject.h
@@ -8,17 +8,18 @@
 #include "ConfigParser.h"
 
 
-#define CONF_CONN(val, str, def, warn) val=GetConfigValue<decltype(val)>(str, def, warn)
-#define CONF_CONN2(val, def, warn) val=GetConfiValue<decltype(val)>(str, def, warn)
-#define GET_CONF(obj, val, str, def, warn) val=obj.GetConfiValue<decltype(val)>(str, def, warn)
-#define GET_CONF2(obj, val, def, warn) val=GetConfiValue<decltype(val)>(#val, def, warn)
+#define CONF_CONN(val, str, def, warn) val=Value<decltype(val)>(str, def, warn)
+#define CONF_CONN2(val, def, warn) val=Value<decltype(val)>(str, def, warn)
+#define GET_CONF(obj, val, str, def, warn) val=obj.Value<decltype(val)>(str, def, warn)
+#define GET_CONF2(obj, val, def, warn) val=Value<decltype(val)>(#val, def, warn)
 
 
 class ConfigObject
 {
 public:
     // constructor, desctructor
-    ConfigObject(const std::string &spliiter = ":=", const std::string &ignore = " _\t", bool case_ins = true);
+    ConfigObject(const std::string &spliiter = ":=", const std::string &ignore = " _\t",
+            const std::string &var_open = "${", const std::string &var_close = "}", bool case_ins = true);
 
     virtual ~ConfigObject();
 
@@ -29,36 +30,37 @@ public:
     bool HasKey(const std::string &name) const;
 
     bool ReadConfigFile(const std::string &path);
-    void ReadConfigString(const std::string &content);
+    void ReadConfigString(const std::string &content, const std::string &path = ".");
     void SetConfigValue(const std::string &var_name, const ConfigValue &c_value);
     void SetIgnoreChars(const std::string &ignore) {ignore_chars = ignore;}
     void SetSplitChars(const std::string &splitter) {split_chars = splitter;}
-    void SetReplacePair(const std::string &open, const std::string &close)
+    void SetVariablePair(const std::string &open, const std::string &close)
     {
-        replace_pair = std::make_pair(open, close);
+        variable_pair = std::make_pair(open, close);
     }
 
     // get members
-    ConfigValue GetConfigValue(const std::string &var_name) const;
+    ConfigValue Value(const std::string &var_name) const;
     template<typename T>
-    T GetConfigValue(const std::string &var_name)
+    T Value(const std::string &var_name)
     const
     {
-        return GetConfigValue(var_name).Convert<T>();
+        return Value(var_name).Convert<T>();
     }
 
-    ConfigValue GetConfigValue(const std::string &var_name, const ConfigValue &def_value, bool verbose = true);
+    ConfigValue Value(const std::string &var_name, const ConfigValue &def_value, bool verbose = true);
     template<typename T>
-    T GetConfigValue(const std::string &var_name, const T &val, bool verbose = true)
+    T Value(const std::string &var_name, const T &val, bool verbose = true)
     {
-        return GetConfigValue(var_name, ConfigValue(val), verbose).Convert<T>();
+        return Value(var_name, ConfigValue(val), verbose).Convert<T>();
     }
 
     const std::string &GetConfigPath() const {return config_path;}
     const std::string &GetSplitChars() const {return split_chars;}
     const std::string &GetSpaceChars() const {return ignore_chars;}
-    const std::pair<std::string, std::string> &GetReplacePair() const {return replace_pair;}
+    const std::pair<std::string, std::string> &GetVariablePair() const {return variable_pair;}
     std::vector<std::string> GetKeyList() const;
+    const std::unordered_map<std::string, std::string> &GetMap() const {return config_map;}
 
 
     // functions that to be overloaded
@@ -77,8 +79,8 @@ private:
 protected:
     std::string split_chars;
     std::string ignore_chars;
+    std::pair<std::string, std::string> variable_pair;
     bool case_insensitive;
-    std::pair<std::string, std::string> replace_pair;
     std::string config_path;
     std::unordered_map<std::string, std::string> config_map;
 
diff --git a/third_party/prconf/include/ConfigParser.h b/third_party/prconf/include/ConfigParser.h
index 5e2810b4b0ea14d3f0167fa05e9089c5542eed1d..65faeb2529d45adcc0ebcefad12ac19b96ad4025 100644
--- a/third_party/prconf/include/ConfigParser.h
+++ b/third_party/prconf/include/ConfigParser.h
@@ -3,10 +3,12 @@
 
 #include <string>
 #include <vector>
+#include <queue>
 #include <deque>
 #include <fstream>
 #include "ConfigValue.h"
 
+
 // a macro to auto generate enum2str and str2enum
 // name mapping begins at bias and continuously increase, split by '|'
 // an example:
@@ -32,79 +34,39 @@ class ConfigParser
 public:
     struct Format
     {
+        struct StrPair { std::string open, close; };
         std::string split;              // element splitters (chars)
         std::string white;              // white spaces (chars)
         std::string delim;              // line delimiter (string)
         std::string glue;               // line glue (string)
-        std::string cmtmark;            // comment marks (string), until the line breaker '\n'
-        std::string cmtopen;            // comment-block opening mark (string)
-        std::string cmtclose;           // comment-block closing mark (string)
-
-        static Format Basic() {return {" \t,", " \t", "\n", "", "", "", ""};}
-        static Format BashLike() {return {" \t,", " \t", "\n", "\\", "#", "\'", "\'"};}
-        static Format CLike() {return {" \t,\n", " \t\n", ";", "", "//", "/*", "*/"};}
-    };
-
-    struct CharBuffer
-    {
-        std::vector<char> data;
-        size_t begin, end;
-
-        CharBuffer(size_t cap = 256) : begin(0), end(0)
-        {
-            data.resize(cap);
-        }
-
-        void Reset() {begin = 0; end = 0; data.clear();}
-        void Add(char ch)
-        {
-            if(data.size() <= end)
-                data.resize(2*data.size());
+        std::string linecmt;            // comment marks (string), until the line breaker '\n'
+        StrPair blockcmt;               // block commenting marks (open string, close string)
+        StrPair quote;                  // quote marks (open string, close string)
 
-            data[end++] = ch;
-        }
-
-        std::string String()
-        const
-        {
-            std::string str;
-            if(end > begin)
-                str.assign(&data[begin], end - begin);
-            return str;
-        }
-
-        inline const char &operator [] (size_t idx) const {return data[idx];}
-        inline char &operator [] (size_t idx) {return data[idx];}
+        static Format Basic() { return {" \t,", " \t", "\n", "", "", {"", ""}, {"\"", "\""}}; }
+        static Format BashLike() { return {" \t,", " \t", "\n", "\\", "#", {"\'", "\'"}, {"\"", "\""}}; }
+        static Format CLike() { return {" \t,\n", " \t\n", ";", "", "//", {"/*", "*/"}, {"\"", "\""}}; }
     };
 
 public:
     ConfigParser(Format f = Format::BashLike());
 
-    ConfigParser(ConfigParser &&that);
-    ConfigParser(const ConfigParser &that);
-
-    virtual ~ConfigParser();
-
-    ConfigParser &operator = (ConfigParser &&rhs);
-    ConfigParser &operator = (const ConfigParser &rhs);
-
     // format related
-    inline void SetFormat(Format &&f) {form = f;}
-    inline void SetFormat(const Format &f) {form = f;}
-    inline void SetSplitters(std::string s) {form.split = s;}
-    inline void SetWhiteSpaces(std::string w) {form.white = w;}
-    inline void SetCommentMark(std::string c) {form.cmtmark = c;}
-    inline void SetCommentPair(std::string o, std::string c) {form.cmtopen = o; form.cmtclose = c;}
-    inline void SetLineGlues(std::string g) {form.glue = g;}
-    inline void SetLineBreaks(std::string b) {form.delim = b;}
-
-    const Format &GetFormat() const {return form;}
+    inline void SetFormat(Format &&f) { fmt = f; }
+    inline void SetFormat(const Format &f) { fmt = f; }
+    inline void SetSplitters(std::string s) { fmt.split = s; }
+    inline void SetWhiteSpaces(std::string w) { fmt.white = w; }
+    inline void SetCommentMark(std::string c) { fmt.linecmt = c; }
+    inline void SetCommentPair(std::string o, std::string c) { fmt.blockcmt = {o, c}; }
+    inline void SetLineGlues(std::string g) { fmt.glue = g; }
+    inline void SetLineBreaks(std::string b) { fmt.delim = b; }
+    inline void SetQuotePair(std::string o, std::string c) { fmt.quote = {o, c}; }
+
+    const Format &GetFormat() const {return fmt;}
 
     // dealing with file/buffer
-    bool OpenFile(const std::string &path, size_t cap = 64*1024);
     bool ReadFile(const std::string &path);
     void ReadBuffer(const char*);
-    void CloseFile();
     void Clear();
 
     // parse line, return false if no more line to parse
@@ -116,9 +78,9 @@ public:
 
     // get current parsing status
     bool CheckElements(int num, int optional = 0);
-    int NbofElements() const {return elements.size();}
-    int LineNumber() const {return line_number;}
-    std::string CurrentLine() const {return cur_line.String();}
+    int NbofElements() const { return elements.size(); }
+    int LineNumber() const { return line_number; }
+    std::string CurrentLine() const;
 
     // take the elements
     ConfigValue TakeFirst();
@@ -179,22 +141,35 @@ public:
 
 private:
     // private functions
-    bool getBuffer();
-    bool getLine(CharBuffer &line_buf, bool recursive = false);
-    int parseBuffer(const CharBuffer &line);
+    void toLines(std::string buf);
+    void parseBuffer();
+    void retrieveLine();
 
 private:
     // private members
-    Format form;
-    std::ifstream infile;
-    CharBuffer buf, cur_line;
+    Format fmt;
     int line_number;
-    std::deque<std::string> elements;
+    std::string curr_line;
+    std::vector<std::string> quotes;
+    std::deque<std::string> lines, elements;
+
 
 public:
     // static functions
     static void comment_line(std::string &str, const std::string &cmt, const std::string &brk);
+    static void comment_line(std::string &str, const std::string &cmt, const std::string &brk,
+                             const std::string &qmark);
     static void comment_between(std::string &str, const std::string &open, const std::string &close);
+    static void comment_between(std::string &str, const std::string &open, const std::string &close,
+                                const std::string &qmark);
+    static void tokenize(std::string &str, std::vector<std::string> &contents, const std::string &token,
+                         const std::string &open, const std::string &close);
+    static inline void tokenize(std::string &str, std::vector<std::string> &contents, const std::string &token,
+                         const std::string &qmark) { tokenize(str, contents, token, qmark, qmark); }
+    static void untokenize(std::string &str, const std::vector<std::string> &contents, const std::string &token,
+                           const std::string &open, const std::string &close);
+    static inline void untokenize(std::string &str, const std::vector<std::string> &contents, const std::string &token,
+                                  const std::string &qmark = "") { untokenize(str, contents, token, qmark, qmark); }
     static std::string trim(const std::string &str, const std::string &w);
     static std::deque<std::string> split(const std::string &str, const std::string &s);
     static std::deque<std::string> split(const char* str, const size_t &len, const std::string &s);
diff --git a/third_party/prconf/include/ConfigValue.h b/third_party/prconf/include/ConfigValue.h
index aaf3a36103cf96af200f2b68f1f330278a8ae46a..425912a7a56ca1800402f93fb8e0c1b0d22cebe3 100644
--- a/third_party/prconf/include/ConfigValue.h
+++ b/third_party/prconf/include/ConfigValue.h
@@ -82,6 +82,8 @@ public:
     const std::string &String() const {return _value;}
     bool IsEmpty() const {return _value.empty();}
 
+    ConfigValue &Trim(const std::string &white);
+
     operator std::string()
     const
     {
diff --git a/third_party/prconf/src/ConfigObject.cpp b/third_party/prconf/src/ConfigObject.cpp
index ee18cd36afcc2b782b33a642e35618379256f7c6..33a98a898862f2bf6c3835427170dfa131efa5a3 100644
--- a/third_party/prconf/src/ConfigObject.cpp
+++ b/third_party/prconf/src/ConfigObject.cpp
@@ -9,6 +9,7 @@
 // 10/31/2016                                                                 //
 //============================================================================//
 
+#include <cstdlib>
 #include <fstream>
 #include <iostream>
 #include <iomanip>
@@ -16,17 +17,17 @@
 #include "ConfigObject.h"
 
 
-
 //============================================================================//
 // Constructor, Destructor                                                    //
 //============================================================================//
 
 // constructor
-ConfigObject::ConfigObject(const std::string &splitter, const std::string &ignore, bool case_ins)
-: split_chars(splitter), ignore_chars(ignore), case_insensitive(case_ins), __empty_value("")
+ConfigObject::ConfigObject(const std::string &splitter, const std::string &ignore, const std::string &var_open,
+        const std::string &var_close, bool case_ins)
+: split_chars(splitter), ignore_chars(ignore), variable_pair({var_open, var_close}), case_insensitive(case_ins),
+  __empty_value("")
 {
-    // set default replace bracket
-    replace_pair = std::make_pair("{", "}");
+    // place holder
 }
 
 // destructor
@@ -79,36 +80,39 @@ bool ConfigObject::ReadConfigFile(const std::string &path)
 }
 
 // read the configuration string directly
-void ConfigObject::ReadConfigString(const std::string &content)
+void ConfigObject::ReadConfigString(const std::string &content, const std::string &path)
 {
     ConfigParser c_parser;
     c_parser.SetSplitters(split_chars);
 
     c_parser.ReadBuffer(content.c_str());
 
-    parserProcess(c_parser, "buffer_string");
+    parserProcess(c_parser, path);
 }
 
 // continue parse the terms
 void ConfigObject::parserProcess(ConfigParser &c_parser, const std::string &source)
 {
-    std::string cur_dir = ConfigParser::decompose_path(source).dir;
+    char abs_path[2048];
+    realpath(source.c_str(), abs_path);
+    std::string cur_dir = ConfigParser::decompose_path(abs_path).dir;
+
+    // THIS_DIR needs special treatment as many files in different dirs may be loaded into one instance
+    std::string dir_key = variable_pair.first + "THIS_DIR" + variable_pair.second;
 
     while (c_parser.ParseLine()) {
         // possible control words
         if (c_parser.NbofElements() == 1) {
             std::string control = c_parser.TakeFirst();
-            size_t pos = control.find("{THIS_DIR}");
-            if(pos != std::string::npos)
-                control.replace(pos, 10, cur_dir);
+            size_t pos = control.find(dir_key);
+            if(pos != std::string::npos) { control.replace(pos, dir_key.size(), cur_dir); }
             parseControl(control);
         // var_name and var_value
         } else if (c_parser.NbofElements() == 2) {
             std::string var_name, key, var_value;
             c_parser >> var_name >> var_value;
-            size_t pos = var_value.find("{THIS_DIR}");
-            if(pos != std::string::npos)
-                var_value.replace(pos, 10, cur_dir);
+            size_t pos = var_value.find(dir_key);
+            if(pos != std::string::npos) { var_value.replace(pos, dir_key.size(), cur_dir); }
             parseTerm(std::move(var_name), std::move(var_value));
         // unsupported format
         } else {
@@ -174,7 +178,7 @@ const
 }
 
 // get configuration value by its name/key
-ConfigValue ConfigObject::GetConfigValue(const std::string &var_name)
+ConfigValue ConfigObject::Value(const std::string &var_name)
 const
 {
     // convert to lower case and remove uninterested characters
@@ -185,7 +189,7 @@ const
         return __empty_value;
     } else {
         ConfigValue result(it->second);
-        reform(result._value, replace_pair.first, replace_pair.second);
+        reform(result._value, variable_pair.first, variable_pair.second);
         return result;
     }
 }
@@ -202,7 +206,7 @@ void ConfigObject::SetConfigValue(const std::string &var_name, const ConfigValue
 
 // get configuration value from the map
 // if no such config value exists, it will fill the default value in
-ConfigValue ConfigObject::GetConfigValue(const std::string &var_name, const ConfigValue &def_value, bool verbose)
+ConfigValue ConfigObject::Value(const std::string &var_name, const ConfigValue &def_value, bool verbose)
 {
     auto key = formKey(var_name);
 
@@ -222,7 +226,7 @@ ConfigValue ConfigObject::GetConfigValue(const std::string &var_name, const Conf
     }
 
     ConfigValue result(it->second);
-    reform(result._value, replace_pair.first, replace_pair.second);
+    reform(result._value, variable_pair.first, variable_pair.second);
     return result;
 }
 
@@ -243,7 +247,7 @@ const
     return key;
 }
 
-// replace the contents inside replace_pair with the configuration value
+// replace the contents inside variable_pair with the configuration value
 void ConfigObject::reform(std::string &input, const std::string &op, const std::string &cl)
 const
 {
@@ -258,17 +262,7 @@ const
             reform(var, op, cl);
 
             // replace content
-            std::string val;
-
-            // environment variable
-            if (rpair.first > 0 && input.at(rpair.first - 1) == '$') {
-                val = std::getenv(var.c_str());
-                // replace $ mark also
-                rpair.first--;
-            // ConfigObject variable
-            } else {
-                val = GetConfigValue(var)._value;
-            }
+            std::string val = HasKey(var) ? Value(var)._value : std::getenv(var.c_str());
 
             // replace variable with configuration value
             input.replace(rpair.first, rpair.second - rpair.first + cl.size(), val);
@@ -304,7 +298,7 @@ void ConfigObject::parseControl(const std::string &word)
         int length = p.second - begin;
 
         std::string new_path = word.substr(begin, length);
-        reform(new_path, replace_pair.first, replace_pair.second);
+        reform(new_path, variable_pair.first, variable_pair.second);
 
         ReadConfigFile(new_path);
     }
diff --git a/third_party/prconf/src/ConfigParser.cpp b/third_party/prconf/src/ConfigParser.cpp
index ad496d48e13bfeacdfda01dd92178482dd778c69..36e63537ace3fee0ce063451926478ea28077542 100644
--- a/third_party/prconf/src/ConfigParser.cpp
+++ b/third_party/prconf/src/ConfigParser.cpp
@@ -12,6 +12,10 @@
 
 using namespace std;
 
+#define TOKEN_DIGITS 5
+
+// TODO radmonize token that has no conflic with the format marks
+static const string config_token = "Gc2xConfig6R";
 
 
 //============================================================================//
@@ -20,114 +24,53 @@ using namespace std;
 
 // constructor, with format input
 ConfigParser::ConfigParser(Format f)
-: form(f), line_number(0)
-{
-    // place holder
-}
-
-// copy constructor, only copy format
-ConfigParser::ConfigParser(const ConfigParser &that)
-: form(that.form), line_number(0)
+: fmt(f), line_number(0)
 {
     // place holder
 }
 
-// move constructor, only move format
-ConfigParser::ConfigParser(ConfigParser &&that)
-: form(that.form), line_number(0)
-{
-    // place holder
-}
-
-// desctructor
-ConfigParser::~ConfigParser()
-{
-    CloseFile();
-}
-
-// copy assignment operator
-ConfigParser &ConfigParser::operator = (const ConfigParser &rhs)
-{
-    form = rhs.form;
-    return *this;
-}
-
-// move assignment operator
-ConfigParser &ConfigParser::operator = (ConfigParser &&rhs)
-{
-    form = rhs.form;
-    return *this;
-}
-
 //============================================================================//
 // Public Member Function                                                     //
 //============================================================================//
 
-// open a file for future parsing
-bool ConfigParser::OpenFile(const string &path, size_t cap)
-{
-    Clear();
-
-    infile.open(path);
-
-    if(!infile.is_open())
-        return false;
-
-    buf.data.resize(cap);
-
-    // success!
-    return true;
-}
-
 // read the whole file into a buffer and break it into lines
 bool ConfigParser::ReadFile(const string &path)
 {
-    Clear();
-
-    infile.open(path);
+    ifstream inf(path);
 
-    if(!infile.is_open())
+    if(!inf.is_open())
         return false;
 
-    infile.seekg(0, ios::end);
-    buf.end = infile.tellg();
-    buf.data.resize(buf.end);
-    infile.seekg(0, ios::beg);
+    // read the whole file in
+    string str;
+
+    inf.seekg(0, ios::end);
+    str.reserve(inf.tellg());
+    inf.seekg(0, ios::beg);
 
-    infile.read(&buf.data[0], buf.end);
-    infile.close();
+    str.assign((istreambuf_iterator<char>(inf)), istreambuf_iterator<char>());
+    inf.close();
+
+    toLines(str);
 
     return true;
 }
 
-// close file
-void ConfigParser::CloseFile()
+// read a buffer in
+void ConfigParser::ReadBuffer(const char *buf_in)
 {
-    return infile.close();
+    toLines(buf_in);
 }
 
 // clear stored lines
 void ConfigParser::Clear()
 {
-    buf.Reset();
+    lines.clear();
+    elements.clear();
 
     // reset line
     line_number = 0;
-    cur_line.Reset();
-
-    // close file
-    CloseFile();
-}
-
-// read a buffer in
-void ConfigParser::ReadBuffer(const char *buf_in)
-{
-    Clear();
-
-    buf.end = strlen(buf_in);
-    buf.data.resize(buf.end + 2);
-
-    strncpy(&buf.data[0], buf_in, buf.end);
+    curr_line.clear();
 }
 
 // parse a line from the file or buffer
@@ -135,57 +78,38 @@ void ConfigParser::ReadBuffer(const char *buf_in)
 // return false if reached the end
 bool ConfigParser::ParseLine()
 {
-    elements.clear();
-
-    while(elements.empty())
-    {
-        if(!getLine(cur_line))
-            return false;
-
-        // count the line number
-        ++line_number;
-
-        parseBuffer(cur_line);
+    if (lines.empty()) {
+        return false;
     }
 
-    return true;
+    elements.clear();
+    parseBuffer();
+
+    return static_cast<bool>(elements.size());
 }
 
+
 // parse the whole file or buffer
 // return false if nothing was found
 bool ConfigParser::ParseAll()
 {
     elements.clear();
 
-    while(true)
-    {
-        if(!getLine(cur_line))
-            return !elements.empty();
-
-        // count the line number
-        ++line_number;
-
-        parseBuffer(cur_line);
+    while (lines.size()) {
+        parseBuffer();
     }
+
+    return static_cast<bool>(elements.size());
 }
 
 // parse an input string, split the string into elements
 // the trail white spaces in the elements will be trimmed
 int ConfigParser::ParseString(const string &line)
 {
-    deque<string> eles = split(line.c_str(), line.size(), form.split);
+    toLines(line);
+    ParseAll();
 
-    int count = 0;
-    for(auto &ele : eles)
-    {
-        string trim_ele = trim(ele, form.white);
-        if(!trim_ele.empty()) {
-            elements.emplace_back(move(trim_ele));
-            count++;
-        }
-    }
-
-    return count;
+    return elements.size();
 }
 
 // check if the current elemtns number is in the range [num, num + optional]
@@ -223,12 +147,20 @@ bool ConfigParser::CheckElements(int num, int optional)
          << line_number
          << ", expecting " << num_str << " elements. "
          << endl
-         << "\"" << cur_line.String() << "\""
+         << "\"" << CurrentLine() << "\""
          << endl;
     return false;
 }
 
 
+// get current line
+string ConfigParser::CurrentLine() const
+{
+    string res = curr_line;
+    untokenize(res, quotes, config_token, fmt.quote.open, fmt.quote.close);
+    return res;
+}
+
 // take the first element
 ConfigValue ConfigParser::TakeFirst()
 {
@@ -249,165 +181,102 @@ ConfigValue ConfigParser::TakeFirst()
 //============================================================================//
 // Private Member Function                                                    //
 //============================================================================//
-
-// get buffer from the file or the input buffer
-// return false if reached input end
-bool ConfigParser::getBuffer()
+// helper function
+inline bool compare_str(const char *buf1, const char *buf2, size_t n)
 {
-    if(buf.begin < buf.end)
-        return true;
-
-    if(!infile.is_open() || infile.bad() || infile.eof())
-        return false;
-
-    infile.read(&buf.data[0], buf.data.size());
-
-    buf.begin = 0;
-    buf.end = infile.gcount();
-
+    for (size_t i = 0; i < n; ++i) {
+        if (buf1[i] != buf2[i]) {
+            return false;
+        }
+    }
     return true;
 }
 
-// trim white spaces
-inline void trimbuf(const vector<char> &buf, size_t &begin, size_t &end, const string &w)
+// helper function
+inline string break_line(const string &buf, const std::string &delim, size_t &i, const std::string &glue)
 {
-    while(begin < end)
-    {
-        if(w.find(buf[begin]) == string::npos)
-            break;
-
-        begin++;
+    size_t beg = i;
+    if (delim.empty()) {
+        i = buf.size();
+        return buf.substr(beg);
     }
 
-    while(end > begin)
-    {
-        if(w.find(buf[end - 1]) == string::npos)
-            break;
-
-        end--;
+    for (; i <= buf.size() - delim.size(); ++i) {
+        if (compare_str(&buf[i], delim.c_str(), delim.size())) {
+            string res = buf.substr(beg, i - beg);
+            i += delim.size();
+            if (glue.size() && (res.size() > glue.size())) {
+                size_t pos = res.size() - glue.size();
+                if (compare_str(&res[pos], glue.c_str(), glue.size())) {
+                    return res.substr(0, pos) + break_line(buf, delim, i, glue);
+                }
+            }
+            return res;
+        }
     }
+    i = buf.size();
+    return buf.substr(beg);
 }
 
-inline bool compare(const char ch, const string &str, size_t &c1)
+// parse the buffer string, remove comments, tokenize quotes, and split it in lines
+void ConfigParser::toLines(string buf)
 {
-    if(str.empty())
-        return false;
+    Clear();
 
-    c1 = (ch == str[c1]) ? (c1 + 1) : 0;
+    if (buf.empty()) {
+        return;
+    }
 
-    return (c1 >= str.size());
+    // break into lines
+    size_t ibuf = 0;
+    while (ibuf < buf.size()) {
+        lines.emplace_back(break_line(buf, fmt.delim, ibuf, fmt.glue));
+    }
 }
 
-inline bool rcompare(const ConfigParser::CharBuffer &buf, const string &str)
+void ConfigParser::retrieveLine()
 {
-    if(str.empty() || buf.end <= buf.begin || buf.end - buf.begin < str.size())
-        return false;
+    curr_line += move(lines.front());
+    lines.pop_front();
+    tokenize(curr_line, quotes, config_token, fmt.quote.open, fmt.quote.close);
+    comment_between(curr_line, fmt.blockcmt.open, fmt.blockcmt.close);
+    comment_line(curr_line, fmt.linecmt, fmt.delim);
 
-    for(size_t i = 1; i <= str.size(); ++i)
-    {
-        if(str[str.size() - i] != buf[buf.end - i])
-            return false;
+    if (lines.empty()) { return; }
+    if (curr_line.empty() || (curr_line.find(fmt.blockcmt.open) != string::npos)) {
+        retrieveLine();
     }
-
-    return true;
 }
 
-// a helper structure to check context status
-struct TextStatus
+// parse buffered lines
+void ConfigParser::parseBuffer()
 {
-    int val;
-    size_t cmt1, cmt2, delim;
+    size_t count = 0;
+    curr_line.clear();
+    quotes.clear();
 
-    TextStatus() : val(0), cmt1(0), cmt2(0), delim(0) {}
-    inline void Set(int i) {val = i; cmt1 = 0; cmt2 = 0;}
-};
+    while (!count && lines.size())
+    {
+        retrieveLine();
 
-// get a line from the file or buffer
-// it deals with comments, white spaces
-// return false if reached the end
-bool ConfigParser::getLine(CharBuffer &line_buf, bool recursive)
-{
-    if(!recursive)
-        line_buf.Reset();
-    bool success = false;
-    TextStatus stat;
+        trim(curr_line, fmt.white);
+        auto eles = split(curr_line, fmt.split);
 
-    while(getBuffer())
-    {
-        success = true;
-
-        while(buf.begin < buf.end)
-        {
-            auto &ch = buf[buf.begin++];
-            switch(stat.val)
-            {
-            default:
-            case 0:
-                line_buf.Add(ch);
-                // check if it is the end
-                if(compare(ch, form.delim, stat.delim)) {
-                    line_buf.end -= form.delim.size();
-                    trimbuf(line_buf.data, line_buf.begin, line_buf.end, form.white);
-                    // glue lines
-                    if(rcompare(line_buf, form.glue)) {
-                        line_buf.end -= form.glue.size();
-                        return getLine(line_buf, true);
-                    } else {
-                        return success;
-                    }
-                } else if(compare(ch, form.cmtopen, stat.cmt1)) {
-                    stat.Set(1);
-                    line_buf.end -= form.cmtopen.size();
-                } else if(compare(ch, form.cmtmark, stat.cmt2)) {
-                    stat.Set(2);
-                    line_buf.end -= form.cmtmark.size();
-                }
-                break;
-            case 1:
-                if(compare(ch, form.cmtclose, stat.cmt1)) {
-                    stat.Set(0);
-                }
-                break;
-            case 2:
-                if(ch == '\n') {
-                    stat.Set(0);
-                    buf.begin -= 1;
-                }
-                break;
+        // trim every element and replace token with info
+        for (auto &ele : eles) {
+            ele = trim(ele, fmt.white);
+            if (ele.empty()) {
+                continue;
             }
+            untokenize(ele, quotes, config_token);
+            elements.emplace_back(move(ele));
+            count++;
         }
+        line_number++;
     }
-
-    trimbuf(line_buf.data, line_buf.begin, line_buf.end, form.white);
-    return success;
 }
 
-// parse an input char buffer, split the string into elements
-// the trail white spaces in the elements will be trimmed
-int ConfigParser::parseBuffer(const CharBuffer &line)
-{
-    if(line.begin >= line.end)
-        return 0;
 
-    size_t ele_begin = line.begin;
-    int count = 0;
-
-    // intended to visit i == line.end, so the rest of the string get parsed
-    for(size_t i = 0; i <= line.end; ++i)
-    {
-        if(i == line.end || form.split.find(line[i]) != string::npos) {
-            size_t ele_end = i;
-            trimbuf(line.data, ele_begin, ele_end, form.white);
-            if(ele_begin < ele_end) {
-                elements.emplace_back(&line[ele_begin], ele_end - ele_begin);
-                count++;
-            }
-            ele_begin = i + 1;
-        }
-    }
-
-    return count;
-}
 
 //============================================================================//
 // Public Static Function                                                     //
@@ -443,22 +312,77 @@ void ConfigParser::comment_line(string &str, const string &c, const string &b)
     }
 }
 
+// comment out a string, consider quotes
+void ConfigParser::comment_line(string &str, const string &c, const string &b, const string &qmark)
+{
+    vector<string> quotes;
+    tokenize(str, quotes, config_token, qmark, qmark);
+    comment_line(str, c, b);
+    untokenize(str, quotes, config_token, qmark, qmark);
+}
+
+// tokenize the content between quote marks, no nested structure supported
+void ConfigParser::tokenize(string &str, vector<string> &contents, const string &token,
+                            const string &open, const string &close)
+{
+    if (str.empty() || open.empty() || close.empty())
+        return;
+
+    string padzero(TOKEN_DIGITS, '0');
+    while (true) {
+        size_t pos1 = str.find(open);
+        if (pos1 != string::npos) {
+            size_t pos2 = str.find(close, pos1 + open.size());
+            // found pair
+            if (pos2 != string::npos) {
+                string digits = (padzero + to_string(contents.size()));
+                contents.emplace_back(str.substr(pos1 + open.size(), pos2 - pos1 - open.size()));
+                str.replace(pos1, pos2 + close.size() - pos1, token + digits.substr(digits.size() - TOKEN_DIGITS));
+            } else {
+                return;
+            }
+        } else {
+            return;
+        }
+    }
+}
+
+// reversal of tokenize
+void ConfigParser::untokenize(string &str, const vector<string> &contents, const string &token,
+                              const string &open, const string &close)
+{
+    if (contents.empty() || str.empty() || token.empty()) {
+        return;
+    }
+
+    auto pos = str.find(token);
+    auto size = token.size() + TOKEN_DIGITS;
+    while ((pos != string::npos) && ((pos + size) <= str.size())) {
+        size_t id = stoul(str.substr(pos + token.size(), TOKEN_DIGITS));
+        if (open.empty() || close.empty()) {
+            str.replace(pos, size, contents[id]);
+        } else {
+            str.replace(pos, size, open + contents[id] + close);
+        }
+        pos = str.find(token);
+    }
+}
+
 // comment out between a pair of comment marks
 // NOTICE: does not support nested structure of comment marks
 void ConfigParser::comment_between(string &str, const string &open, const string &close)
 {
     // no need to continue
-    if(str.empty() || open.empty() || close.empty())
+    if (str.empty() || open.empty() || close.empty())
         return;
 
-    while(true)
-    {
+    while (true) {
         // find the openning comment mark
         size_t pos1 = str.find(open);
-        if(pos1 != string::npos) {
+        if (pos1 != string::npos) {
             size_t pos2 = str.find(close, pos1 + open.size());
             // found pair
-            if(pos2 != string::npos) {
+            if (pos2 != string::npos) {
                 // remove everything between, including this pair
                 str.erase(pos1, pos2 + close.size() - pos1);
             // comment pair not found
@@ -472,6 +396,15 @@ void ConfigParser::comment_between(string &str, const string &open, const string
     }
 }
 
+// comment out a string, consider quotes
+void ConfigParser::comment_between(string &str, const string &open, const string &close, const string &qmark)
+{
+    vector<string> quotes;
+    tokenize(str, quotes, config_token, qmark, qmark);
+    comment_between(str, open, close);
+    untokenize(str, quotes, config_token, qmark, qmark);
+}
+
 // trim all the characters defined as white space at both ends
 string ConfigParser::trim(const string &str, const string &w)
 {
diff --git a/third_party/prconf/src/ConfigValue.cpp b/third_party/prconf/src/ConfigValue.cpp
index 96bd4b2ead9db0fe957b8cb11cb1c260f4e75fae..632d88f558943474e49afd5d211f7ed013c67ffb 100644
--- a/third_party/prconf/src/ConfigValue.cpp
+++ b/third_party/prconf/src/ConfigValue.cpp
@@ -305,6 +305,19 @@ const
     return _value.c_str();
 }
 
+ConfigValue &ConfigValue::Trim(const std::string &white)
+{
+    const auto strBegin = _value.find_first_not_of(white);
+    if (strBegin == string::npos) {
+        _value = "";
+    } else {
+        const auto strEnd = _value.find_last_not_of(white);
+        const auto strRange = strEnd - strBegin + 1;
+        _value = _value.substr(strBegin, strRange);
+    }
+
+    return *this;
+}
 
 
 //============================================================================//