From d88c486ae403bee8f4b16e4bdf9daf19f8915eed Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Sat, 26 Sep 2009 02:09:03 +0200 Subject: Client: Added unfinished XLS import command --- src/Client/CMakeLists.txt | 2 + src/Client/UserListCommands.cpp | 268 +++++++++++++++++++++++++++++++--------- src/Client/UserListCommands.h | 12 ++ src/Client/XLSReader.cpp | 89 +++++++++++++ src/Client/XLSReader.h | 57 +++++++++ src/Client/XLSSheet.cpp | 109 ++++++++++++++++ src/Client/XLSSheet.h | 91 ++++++++++++++ src/madc.cpp | 2 +- 8 files changed, 571 insertions(+), 59 deletions(-) create mode 100644 src/Client/XLSReader.cpp create mode 100644 src/Client/XLSReader.h create mode 100644 src/Client/XLSSheet.cpp create mode 100644 src/Client/XLSSheet.h diff --git a/src/Client/CMakeLists.txt b/src/Client/CMakeLists.txt index 4e83c4e..30fb722 100644 --- a/src/Client/CMakeLists.txt +++ b/src/Client/CMakeLists.txt @@ -22,5 +22,7 @@ mad_library(Client SystemCommands.cpp SystemCommands.h UserCommands.cpp UserCommands.h UserListCommands.cpp UserListCommands.h + XLSReader.cpp XLSReader.h + XLSSheet.cpp XLSSheet.h ) target_link_libraries(Client Common Net Core) diff --git a/src/Client/UserListCommands.cpp b/src/Client/UserListCommands.cpp index ad0c2d0..7774240 100644 --- a/src/Client/UserListCommands.cpp +++ b/src/Client/UserListCommands.cpp @@ -19,6 +19,8 @@ #include "UserListCommands.h" #include "Application.h" +#include "XLSReader.h" +#include "XLSSheet.h" #include "Requests/UserLists/UserListListRequest.h" #include "Requests/UserLists/UserListDownloadRequest.h" #include "Requests/UserLists/UserListDiffListRequest.h" @@ -36,6 +38,7 @@ const UserListCommands::Command UserListCommands::commands[] = { {{"help", "?", 0}, "help [command]", "Display usage information about user list commands", "Display usage information about a user list command. If no command is given, display a list of all available user list commands.", &UserListCommands::helpCommand}, {{"list", "ls", 0}, "list", "List the stored user lists", "List the stored user lists.", &UserListCommands::listCommand}, {{"show_list", "sl", 0}, "show_list list", "Displays a user list", "Displays a user list.", &UserListCommands::showListCommand}, + {{"import", "im", 0}, "import filename", "Imports a user list file", "Imports a user list file. At the moment, this supports only XLS files.", &UserListCommands::importCommand}, {{0}, 0, 0, 0, 0} }; @@ -173,90 +176,239 @@ void UserListCommands::showListCommand(CommandParser *commandParser, const std:: std::cout << "s"; std::cout << "." << std::endl << std::endl; - std::map lengths; + printUserList(*userList); + } - static const std::string USER_NAME("User name"); - lengths.insert(std::make_pair(USER_NAME, USER_NAME.length())); - static const std::string GROUP_NAME("Group name"); - lengths.insert(std::make_pair(GROUP_NAME, GROUP_NAME.length())); + std::cout << std::endl; + } + else { + std::cerr << args[0] << " " << args[1] << ": Too many arguments." << std::endl; + printUsage("show_list"); + return; + } +} - std::set details = userList->getDetails(); - for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { - lengths.insert(std::make_pair(*detail, detail->length())); +void UserListCommands::importCommand(CommandParser* /*commandParser*/, const std::vector &args) { + if(args.size() < 3) { + std::cerr << args[0] << " " << args[1] << ": No filename given." << std::endl; + printUsage("import"); + return; + } + else if(args.size() == 3) { + try { + XLSReader reader(args[2]); + const std::list > &sheets = reader.getSheets(); + + if(sheets.empty()) { + std::cerr << "There aren't any worksheets in the file." << std::endl; + return; } - for(Common::UserLists::UserList::iterator user = userList->begin(); user != userList->end(); ++user) { - std::map::iterator it = lengths.find(USER_NAME); - if(user->getName().length() > it->second) - it->second = user->getName().length(); + unsigned long sheetNum = 1; + + if(sheets.size() > 1) { + std::cout << "There is more than one worksheet in the file. Which one do you want to import?" << std::endl << std::endl; + + while(true) { + int i = 1; + for(std::list >::const_iterator sheet = sheets.begin(); sheet != sheets.end(); ++sheet, ++i) { + std::cout << " " << i << ") " << (*sheet)->getTitle() << std::endl; + } - it = lengths.find(GROUP_NAME); - if(user->getGroup().length() > it->second) - it->second = user->getGroup().length(); + std::cout << std::endl; + std::cout << "Worksheet number: " << std::flush; - for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { - it = lengths.find(*detail); - std::string detailStr = user->getDetail(*detail); - if(detailStr.length() > it->second) - it->second = detailStr.length(); + std::string input; + std::getline(std::cin, input); + sheetNum = std::strtoul(input.c_str(), 0, 10); + + if(sheetNum > 0 && sheetNum <= sheets.size()) + break; + + std::cout << "Invalid selection. Please choose one of:" << std::endl << std::endl; } } - std::cout << " "; - printPadded(USER_NAME, lengths.find(USER_NAME)->second); - std::cout << " "; - printPadded(GROUP_NAME, lengths.find(GROUP_NAME)->second); - for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { - std::cout << " "; - printPadded(*detail, lengths.find(*detail)->second); - } - std::cout << std::endl; - - std::cout << "-"; - printDelimiter(lengths.find(USER_NAME)->second); - std::cout << "- -"; - printDelimiter(lengths.find(GROUP_NAME)->second); - for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { - std::cout << "- -"; - printDelimiter(lengths.find(*detail)->second); - } - std::cout << "-" << std::endl; + std::list >::const_iterator sheet = sheets.begin(); + std::advance(sheet, sheetNum-1); - for(Common::UserLists::UserList::iterator user = userList->begin(); user != userList->end(); ++user) { - static const std::string UNSET(""); + std::cout << "Imported worksheet:" << std::endl; + printSheet(**sheet, 10); - std::cout << " "; + std::cout << std::endl << std::endl; + std::cout << "Does the first line of the worksheet contain the column names? (Y/n) " << std::flush; - if(!user->getName().empty()) - printPadded(user->getName(), lengths.find(USER_NAME)->second); - else - printPadded(UNSET, lengths.find(USER_NAME)->second); + std::string input; + std::getline(std::cin, input); + if(input.empty() || (input[0] != 'n' && input[0] != 'N')) { + (*sheet)->useFirstRowAsColumnNames(true); + } + + std::cout << std::endl << std ::endl; + printSheet(**sheet, 10); + std::cout << std::endl << std ::endl; + + Common::UserLists::UserList userList; + const std::vector &rows = (*sheet)->getRows(); + unsigned id = 1; - std::cout << " "; + for(std::vector::const_iterator rowIt = rows.begin(); rowIt != rows.end(); ++rowIt, ++id) { + const std::vector &row = **rowIt; + Common::UserLists::UserListEntry entry; - if(!user->getGroup().empty()) - printPadded(user->getGroup(), lengths.find(GROUP_NAME)->second); - else - printPadded(UNSET, lengths.find(GROUP_NAME)->second); + std::ostringstream stream; + stream << id; + entry.setName(stream.str()); - for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { - std::cout << " "; - printPadded(user->getDetail(*detail), lengths.find(*detail)->second); + for(unsigned col = 0; col < (*sheet)->getColumnCount(); ++col) { + entry.setDetail((*sheet)->getColumnName(col), row[col]); } - std::cout << std::endl; + userList.addUser(entry); } - } - std::cout << std::endl; + printUserList(userList); + std::cout << std::endl << std ::endl; + } + catch(Core::Exception e) { + std::cerr << "The file can't be read: " << e.what() << "." << std::endl; + } } else { std::cerr << args[0] << " " << args[1] << ": Too many arguments." << std::endl; - printUsage("show_list"); + printUsage("import"); return; } } + +void UserListCommands::printUserList(const Common::UserLists::UserList &list) { + std::map lengths; + + static const std::string USER_NAME = "User name"; + static const std::string GROUP_NAME = "Group name"; + + lengths.insert(std::make_pair(USER_NAME, USER_NAME.length())); + lengths.insert(std::make_pair(GROUP_NAME, GROUP_NAME.length())); + + std::set details = list.getDetails(); + for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { + lengths.insert(std::make_pair(*detail, detail->length())); + } + + for(Common::UserLists::UserList::const_iterator user = list.begin(); user != list.end(); ++user) { + std::map::iterator it = lengths.find(USER_NAME); + if(user->getName().length() > it->second) + it->second = user->getName().length(); + + it = lengths.find(GROUP_NAME); + if(user->getGroup().length() > it->second) + it->second = user->getGroup().length(); + + for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { + it = lengths.find(*detail); + std::string detailStr = user->getDetail(*detail); + if(detailStr.length() > it->second) + it->second = detailStr.length(); + } + } + + std::cout << " "; + printPadded(USER_NAME, lengths.find(USER_NAME)->second); + std::cout << " "; + printPadded(GROUP_NAME, lengths.find(GROUP_NAME)->second); + for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { + std::cout << " "; + printPadded(*detail, lengths.find(*detail)->second); + } + std::cout << std::endl; + + std::cout << "-"; + printDelimiter(lengths.find(USER_NAME)->second); + std::cout << "- -"; + printDelimiter(lengths.find(GROUP_NAME)->second); + for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { + std::cout << "- -"; + printDelimiter(lengths.find(*detail)->second); + } + std::cout << "-" << std::endl; + + for(Common::UserLists::UserList::const_iterator user = list.begin(); user != list.end(); ++user) { + static const std::string UNSET(""); + + std::cout << " "; + + if(!user->getName().empty()) + printPadded(user->getName(), lengths.find(USER_NAME)->second); + else + printPadded(UNSET, lengths.find(USER_NAME)->second); + + std::cout << " "; + + if(!user->getGroup().empty()) + printPadded(user->getGroup(), lengths.find(GROUP_NAME)->second); + else + printPadded(UNSET, lengths.find(GROUP_NAME)->second); + + for(std::set::iterator detail = details.begin(); detail != details.end(); ++detail) { + std::cout << " "; + printPadded(user->getDetail(*detail), lengths.find(*detail)->second); + } + + std::cout << std::endl; + } +} + +void UserListCommands::printSheet(const XLSSheet &sheet, unsigned rowCount) { + const std::vector &rows = sheet.getRows(); + std::vector lengths(sheet.getColumnCount()); + + for(unsigned col = 0; col < sheet.getColumnCount(); ++col) { + lengths[col] = sheet.getColumnName(col).size(); + } + + for(std::vector::const_iterator rowIt = rows.begin(); rowIt != rows.end() && (rowCount == 0 || rowIt != rows.begin()+rowCount); ++rowIt) { + const std::vector &row = **rowIt; + + for(unsigned col = 0; col < sheet.getColumnCount(); ++col) { + if(row[col].size() > lengths[col]) + lengths[col] = row[col].size(); + } + } + + for(unsigned col = 0; col < sheet.getColumnCount(); ++col) { + std::cout << " "; + printPadded(sheet.getColumnName(col), lengths[col]); + } + std::cout << std::endl; + + std::cout << " "; + for(unsigned col = 0; col < sheet.getColumnCount(); ++col) { + std::cout << " -"; + printDelimiter(lengths[col]); + std::cout << "-"; + } + std::cout << std::endl; + + for(std::vector::const_iterator rowIt = rows.begin(); rowIt != rows.end() && (rowCount == 0 || rowIt != rows.begin()+rowCount); ++rowIt) { + const std::vector &row = **rowIt; + + for(unsigned col = 0; col < sheet.getColumnCount(); ++col) { + std::cout << " "; + printPadded(row[col], lengths[col]); + } + + std::cout << std::endl; + } + + if(rowCount > 0 && rows.size() > rowCount) { + for(unsigned col = 0; col < sheet.getColumnCount(); ++col) { + std::cout << " "; + printPadded("...", lengths[col]); + } + } +} + void UserListCommands::printPadded(const std::string &str, unsigned length) { std::cout << str; if(str.length() < length) diff --git a/src/Client/UserListCommands.h b/src/Client/UserListCommands.h index 51a714b..8c77e7f 100644 --- a/src/Client/UserListCommands.h +++ b/src/Client/UserListCommands.h @@ -25,8 +25,17 @@ #include "CommandParser.h" namespace Mad { + +namespace Common { +namespace UserLists { +class UserList; +} +} + namespace Client { +class XLSSheet; + class MAD_CLIENT_EXPORT UserListCommands { private: typedef CommandParser::Command Command; @@ -41,7 +50,10 @@ class MAD_CLIENT_EXPORT UserListCommands { static void helpCommand(CommandParser *commandParser, const std::vector &args); static void listCommand(CommandParser *commandParser, const std::vector &args); static void showListCommand(CommandParser *commandParser, const std::vector &args); + static void importCommand(CommandParser *commandParser, const std::vector &args); + static void printUserList(const Common::UserLists::UserList &list); + static void printSheet(const XLSSheet &sheet, unsigned rowCount = 0); static void printPadded(const std::string &str, unsigned length); static void printDelimiter(unsigned length); diff --git a/src/Client/XLSReader.cpp b/src/Client/XLSReader.cpp new file mode 100644 index 0000000..9bd9dda --- /dev/null +++ b/src/Client/XLSReader.cpp @@ -0,0 +1,89 @@ +/* + * XLSReader.cpp + * + * Copyright (C) 2009 Matthias Schiffer + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along + * with this program. If not, see . + */ + +#include "XLSReader.h" +#include "XLSSheet.h" + +#include +#include +#include + +namespace Mad { +namespace Client { + +xmlNodePtr XLSReader::findNode(xmlNodePtr parent, const std::string &name) { + if(!parent) + return 0; + + for(xmlNodePtr entry = parent->children; entry != 0; entry = entry->next) { + if(entry->type == XML_ELEMENT_NODE && !xmlStrcmp(entry->name, (xmlChar*)name.c_str())) { + return entry; + } + } + + return 0; +} + +XLSReader::XLSReader(const std::string &filename) throw (Core::Exception) { + static const std::string XLHTML_EXEC = "xlhtml -xml"; + static const boost::regex r("'"); + + if(!boost::filesystem::exists(filename)) + throw Core::Exception(Core::Exception::NOT_FOUND); + + std::string escapedFilename = boost::regex_replace(filename, r, "\\\\'", boost::match_default); + + std::FILE *stream = popen((XLHTML_EXEC + " '" + escapedFilename + "' 2>&1").c_str(), "r"); + + std::string data; + + while(!std::feof(stream)) { + char buffer[1024]; + + size_t bytes = std::fread(buffer, 1, sizeof(buffer), stream); + if(!bytes) + break; + + data += std::string(buffer, bytes); + } + + int ret = pclose(stream); + if(!WIFEXITED(ret) || WEXITSTATUS(ret) != 0) { + throw Core::Exception(Core::Exception::INVALID_INPUT); + } + + doc.reset(xmlParseMemory(data.c_str(), data.length()), xmlFreeDoc); + if(!doc) { + throw Core::Exception(Core::Exception::NOT_AVAILABLE); + } + + xmlNodePtr sheetsNode = findNode(xmlDocGetRootElement(doc.get()), "sheets"); + + if(!sheetsNode) { + throw Core::Exception(Core::Exception::NOT_AVAILABLE); + } + + for(xmlNodePtr entry = sheetsNode->children; entry != 0; entry = entry->next) { + if(entry->type == XML_ELEMENT_NODE && !xmlStrcmp(entry->name, (xmlChar*)"sheet")) + sheets.push_back(boost::shared_ptr(new XLSSheet(doc, entry))); + } +} + +} +} diff --git a/src/Client/XLSReader.h b/src/Client/XLSReader.h new file mode 100644 index 0000000..8c7b11a --- /dev/null +++ b/src/Client/XLSReader.h @@ -0,0 +1,57 @@ +/* + * XLSReader.h + * + * Copyright (C) 2009 Matthias Schiffer + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along + * with this program. If not, see . + */ + +#ifndef MAD_CLIENT_XLSREADER_H_ +#define MAD_CLIENT_XLSREADER_H_ + +#include "export.h" + +#include + +#include +#include +#include +#include + +namespace Mad { +namespace Client { + +class XLSSheet; + +class MAD_CLIENT_EXPORT XLSReader : private boost::noncopyable { + private: + friend class XLSSheet; + + boost::shared_ptr doc; + std::list > sheets; + + static xmlNodePtr findNode(xmlNodePtr parent, const std::string &name); + + public: + XLSReader(const std::string &filename) throw (Core::Exception); + + const std::list >& getSheets() const { + return sheets; + } +}; + +} +} + +#endif /* MAD_CLIENT_XLSREADER_H_ */ diff --git a/src/Client/XLSSheet.cpp b/src/Client/XLSSheet.cpp new file mode 100644 index 0000000..915234f --- /dev/null +++ b/src/Client/XLSSheet.cpp @@ -0,0 +1,109 @@ +/* + * XLSSheet.cpp + * + * Copyright (C) 2009 Matthias Schiffer + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along + * with this program. If not, see . + */ + +#include "XLSSheet.h" +#include "XLSReader.h" + +#include +#include + +namespace Mad { +namespace Client { + +std::string XLSSheet::getSheetLevelField(const std::string &name) const { + xmlNodePtr entry = XLSReader::findNode(node, name); + if(entry) + return std::string((char*)xmlNodeGetContent(entry)); + else + return std::string(); +} + +void XLSSheet::readRows() { + rows.clear(); + + xmlNodePtr rowsNode = XLSReader::findNode(node, "rows"); + + xmlNodePtr rowEntry = rowsNode->children; + + if(firstRowColNames) { + for(; rowEntry != 0; rowEntry = rowEntry->next) { + if(rowEntry->type != XML_ELEMENT_NODE || xmlStrcmp(rowEntry->name, (xmlChar*)"row")) + continue; + + for(xmlNodePtr cellEntry = rowEntry->children; cellEntry != 0; cellEntry = cellEntry->next) { + if(cellEntry->type != XML_ELEMENT_NODE || xmlStrcmp(cellEntry->name, (xmlChar*)"cell")) + continue; + + xmlChar *colStr = xmlGetProp(cellEntry, (xmlChar*)"col"); + if(!colStr) + continue; + + unsigned long col = strtoul((char*)colStr, 0, 10); + if(col >= colCount) + continue; + + colNames[col] = (char*)xmlNodeGetContent(cellEntry); + } + + // Skip the first row + rowEntry = rowEntry->next; + break; + } + } + + for(; rowEntry != 0; rowEntry = rowEntry->next) { + if(rowEntry->type != XML_ELEMENT_NODE || xmlStrcmp(rowEntry->name, (xmlChar*)"row")) + continue; + + boost::shared_ptr > rowVector(new std::vector(colCount)); + rows.push_back(rowVector); + + for(xmlNodePtr cellEntry = rowEntry->children; cellEntry != 0; cellEntry = cellEntry->next) { + if(cellEntry->type != XML_ELEMENT_NODE || xmlStrcmp(cellEntry->name, (xmlChar*)"cell")) + continue; + + xmlChar *colStr = xmlGetProp(cellEntry, (xmlChar*)"col"); + if(!colStr) + continue; + + unsigned long col = strtoul((char*)colStr, 0, 10); + if(col >= colCount) + continue; + + (*rowVector)[col] = (char*)xmlNodeGetContent(cellEntry); + } + } +} + +XLSSheet::XLSSheet(boost::shared_ptr doc0, xmlNodePtr node0) : doc(doc0), node(node0), firstRowColNames(false) { + title = getSheetLevelField("pagetitle"); + colCount = std::strtoul(getSheetLevelField("lastcol").c_str(), 0, 10)+1-std::strtoul(getSheetLevelField("firstcol").c_str(), 0, 10); + + for(unsigned col = 0; col < colCount; ++col) { + std::ostringstream stream; + stream << col; + + colNames.push_back(stream.str()); + } + + readRows(); +} + +} +} diff --git a/src/Client/XLSSheet.h b/src/Client/XLSSheet.h new file mode 100644 index 0000000..3038fdc --- /dev/null +++ b/src/Client/XLSSheet.h @@ -0,0 +1,91 @@ +/* + * XLSSheet.h + * + * Copyright (C) 2009 Matthias Schiffer + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along + * with this program. If not, see . + */ + +#ifndef MAD_CLIENT_XLSSHEET_H_ +#define MAD_CLIENT_XLSSHEET_H_ + +#include "export.h" + +#include +#include + +#include +#include +#include +#include + +namespace Mad { +namespace Client { + +class XLSReader; + +class MAD_CLIENT_EXPORT XLSSheet : private boost::noncopyable { + public: + typedef boost::shared_ptr > RowType; + + private: + friend class XLSReader; + + boost::shared_ptr doc; + xmlNodePtr node; + + std::string title; + size_t colCount; + + std::vector rows; + std::vector colNames; + + bool firstRowColNames; + + XLSSheet(boost::shared_ptr doc0, xmlNodePtr node0); + + std::string getSheetLevelField(const std::string &name) const; + + void readRows(); + + public: + void useFirstRowAsColumnNames(bool useFirstRowColNames) { + if(firstRowColNames == useFirstRowColNames) + return; + + firstRowColNames = useFirstRowColNames; + readRows(); + } + + const std::string& getTitle() const { + return title; + } + + size_t getColumnCount() const { + return colCount; + } + + const std::string& getColumnName(unsigned col) const { + return colNames[col]; + } + + const std::vector& getRows() const { + return rows; + } +}; + +} +} + +#endif /* MAD_CLIENT_XLSSHEET_H_ */ diff --git a/src/madc.cpp b/src/madc.cpp index fc95266..eaa1613 100644 --- a/src/madc.cpp +++ b/src/madc.cpp @@ -127,7 +127,7 @@ int main(int argc, char *argv[]) { EditLine *el = el_init(argv[0], stdin, stdout, stderr); - el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_EDITOR, "vi"); el_set(el, EL_PROMPT, &prompt); HistEvent histEv; -- cgit v1.2.3