diff --git a/config/ax_require_defined.m4 b/config/ax_require_defined.m4 new file mode 100644 index 000000000..17c3eab7d --- /dev/null +++ b/config/ax_require_defined.m4 @@ -0,0 +1,37 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_require_defined.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_REQUIRE_DEFINED(MACRO) +# +# DESCRIPTION +# +# AX_REQUIRE_DEFINED is a simple helper for making sure other macros have +# been defined and thus are available for use. This avoids random issues +# where a macro isn't expanded. Instead the configure script emits a +# non-fatal: +# +# ./configure: line 1673: AX_CFLAGS_WARN_ALL: command not found +# +# It's like AC_REQUIRE except it doesn't expand the required macro. +# +# Here's an example: +# +# AX_REQUIRE_DEFINED([AX_CHECK_LINK_FLAG]) +# +# LICENSE +# +# Copyright (c) 2014 Mike Frysinger +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 2 + +AC_DEFUN([AX_REQUIRE_DEFINED], [dnl + m4_ifndef([$1], [m4_fatal([macro ]$1[ is not defined; is a m4 file missing?])]) +])dnl AX_REQUIRE_DEFINED diff --git a/config/sst_check_curses.m4 b/config/sst_check_curses.m4 new file mode 100644 index 000000000..01e58433a --- /dev/null +++ b/config/sst_check_curses.m4 @@ -0,0 +1,52 @@ +AC_DEFUN([SST_CHECK_CURSES], +[ + sst_check_curses_happy="yes" + + AC_ARG_WITH([curses], + [AS_HELP_STRING([--with-ncurses@<:@=DIR@:>@], + [Use ncurses library found in DIR])]) + + AS_IF([test "$with_curses" = "no"], [sst_check_curses_happy="no"]) + + CPPFLAGS_saved="$CPPFLAGS" + LDFLAGS_saved="$LDFLAGS" + + dnl Use user-defined curses library + AS_IF([test "$sst_check_curses_happy" = "yes"], [ + AS_IF([test ! -z "$with_curses" -a "$with_curses" != "yes"], + [CURSES_CPPFLAGS="-I$with_curses/include" + CPPFLAGS="$CURSES_CPPFLAGS $CPPFLAGS" + CURSES_LDFLAGS="-L$with_curses/lib" + CURSES_LIBS="-lncurses", + LDFLAGS="$CURSES_LDFLAGS $LDFLAGS"], + [CURSES_CPPFLAGS= + CURSES_CPPFLAGS_LDFLAGS= + CURSES_LIBS=])]) + +dnl Check for curses.h header + AC_LANG_PUSH([C++]) + AC_CHECK_HEADER([curses.h], [], [sst_check_curses_happy="no"]) + AC_LANG_POP([C++]) + +dnl Check that library is usable +AS_IF([test "$sst_check_curses_happy" != "no"], + [AC_CHECK_LIB([ncursesw], [wprintw], [CURSES_LIBS="-lncursesw"], + [AC_CHECK_LIB([ncurses], [wprintw], [CURSES_LIBS="-lncurses"], + [AC_CHECK_LIB([curses], [wprintw], [CURSES_LIBS="-lcurses"], [sst_check_curses_happy = "no"])])]) + ]) + + CPPFLAGS="$CPPFLAGS_saved" + LDFLAGS="$LDFLAGS_saved" + + AC_SUBST([CURSES_CPPFLAGS]) + AC_SUBST([CURSES_LDFLAGS]) + AC_SUBST([CURSES_LIBS]) + AS_IF([test "x$sst_check_curses_happy" = "xyes"], [AC_DEFINE([HAVE_CURSES], [1], [Defines whether we have the curses library])]) + AM_CONDITIONAL([USE_CURSES], [test "x$sst_check_curses_happy" = "xyes"]) + + AC_MSG_CHECKING([for curses library]) + AC_MSG_RESULT([$sst_check_curses_happy]) + + AS_IF([test "$sst_check_curses_happy" = "no" -a ! -z "$with_curses" -a "$with_curses" != "no"], [$3]) + AS_IF([test "$sst_check_curses_happy" = "yes"], [$1], [$2]) +]) diff --git a/configure.ac b/configure.ac index 8513da91b..496d957b3 100644 --- a/configure.ac +++ b/configure.ac @@ -64,6 +64,8 @@ SST_CHECK_MPI([], [AC_MSG_ERROR([Could not find MPI package])]) SST_CHECK_PYTHON([], [AC_MSG_ERROR([Could not find Python, this is required for SST to build])]) SST_CHECK_LIBZ([have_zlib="yes"],[have_zlib="no"],[AC_MSG_ERROR([zlib was requested but could not be found.])]) +SST_CHECK_CURSES() +dnl AC_MSG_ERROR([Could not find curses, this is required for utility sst-info to build])]) SST_CHECK_BACKTRACE() SST_CHECK_HDF5() SST_CHECK_MEM_POOL() @@ -237,4 +239,10 @@ else printf "%38s : No\n" "libz compression library" fi +if test "x$sst_check_curses_happy" = "xyes" ; then + printf "%38s : Yes\n" "curses library" +else + printf "%38s : No\n" "curses library" +fi + echo "-------------------------------------------------------" diff --git a/src/sst/core/Makefile.am b/src/sst/core/Makefile.am index c82b1c050..5e89af571 100644 --- a/src/sst/core/Makefile.am +++ b/src/sst/core/Makefile.am @@ -341,6 +341,12 @@ sstsim_x_LDADD += $(LIBZ_LIBS) sstinfo_x_LDADD += $(LIBZ_LIBS) endif +if USE_CURSES +AM_CPPFLAGS += $(CURSES_CFPPLAGS) +sstinfo_x_LDADD += $(CURSES_LIBS) +sstinfo_x_LDFLAGS += $(CURSES_LDFLAGS) +endif + if USE_HDF5 AM_CPPFLAGS += $(HDF5_CFLAGS) sstsim_x_SOURCES += statapi/statoutputhdf5.cc diff --git a/src/sst/core/sstinfo.cc b/src/sst/core/sstinfo.cc index 9e6e96d27..1ae97c0e8 100644 --- a/src/sst/core/sstinfo.cc +++ b/src/sst/core/sstinfo.cc @@ -36,13 +36,25 @@ using namespace std; using namespace SST; using namespace SST::Core; -// Global Variables +// General Global Variables static int g_fileProcessedCount; static std::string g_searchPath; static std::vector g_libInfoArray; static SSTInfoConfig g_configuration(false); static std::map g_foundGenerators; +// Interactive Global Variables +#ifdef HAVE_CURSES +#include +static InteractiveWindow g_window; +static std::vector g_infoText; +static std::deque g_prevInput; +static std::vector g_libraryNames; +static unsigned int g_textPos; +static bool g_popupEnabled; +#endif + + void dprintf(FILE* fp, const char* fmt, ...) { @@ -82,13 +94,28 @@ class OverallOutputter void outputXML(); } g_Outputter; +struct SearchableData +{ + const std::map componentTags = { { "description", " Description" }, + { "version", " ELI Version" }, + { "compiledate", " Compiled on" }, + { "category", " Category" } }; + // Can add more in the future +} g_searchData; + // Forward Declarations void initLTDL(const std::string& searchPath); void shutdownLTDL(); static void processSSTElementFiles(); void outputSSTElementInfo(); void generateXMLOutputFile(); +std::string parseInput(std::string); +std::string listLibraryInfo(std::list); +std::string findLibraryInfo(std::list); +void setInfoText(std::string); + +#ifndef HAVE_CURSES int main(int argc, char* argv[]) { @@ -99,14 +126,252 @@ main(int argc, char* argv[]) return 1; } + // Process all specified libraries g_searchPath = g_configuration.getLibPath(); + processSSTElementFiles(); - // Read in the Element files and process them + // Run interactive mode + if ( g_configuration.interactiveEnabled() ) { + std::cout << "Curses library not found. Run SST-Info without the -i flag." << endl; + } + + return 0; +} + +#else +int +main(int argc, char* argv[]) +{ + // Parse the Command Line and get the Configuration Settings + int status = g_configuration.parseCmdLine(argc, argv); + if ( status ) { + if ( status == 1 ) return 0; + return 1; + } + + // Process all specified libraries + g_searchPath = g_configuration.getLibPath(); processSSTElementFiles(); + // Run interactive mode + if ( g_configuration.interactiveEnabled() ) { g_window.start(); } + return 0; } +void +convertToLower(std::string input) +{ + transform(input.begin(), input.end(), input.begin(), ::tolower); +} + +std::string +parseInput(std::string input) +{ + // Split into set of strings + std::istringstream stream(input); + std::string word; + std::vector inputWords; + + while ( stream >> word ) { + inputWords.push_back(word); + } + + // Parse + std::string text = "\n"; + std::string command = inputWords[0]; + convertToLower(command); + + // Help messages + if ( inputWords.size() == 1 ) { + if ( command == "help" ) { + text += + "=== SST-INFO ===\n" + "This program lists documented Components, SubComponents, Events, Modules, and Partitioners within an " + "Element Library.\n\n" + "=== CONTROLS ===\n" + "The 'Console' window contains a command-line style input box. Typed input will appear here.\n" + "The text window can be resized, and both arrow key and mouse scrolling is enabled.\n" + "PAGE UP/PAGE DOWN scrolls through previously entered commands.\n\n" + //"TAB opens and closes the autofill window. Select avaiable input using the arrow keys.\n\n" + "=== COMMANDS ===\n" + "- Help : Displays this help message\n" + "- List {element.subelement} : Displays element libraries and component information\n" + "- Find {field} {search string} : Displays all components with the given search string in its field\n\n" + "To see more detailed instructions, type in a command without additional parameters.\n\n"; + } + else if ( command == "list" ) { + text += "=== LIST ===\n" + "Displays specified element libraries.\n\n" + "=== USAGE ===\n" + "- List all : Display all available element libraries and their components/subcomponents\n" + "- List [element[.component|subcomponent]] : Display the specified element/subelement(s)\n\n" + "'element' - Element Library\n" + "'type' - Type of Component/Subcomponent\n" + "'component|subcomponent' - Either a Component or SubComponent defined in the Element Library\n\n" + "=== EXAMPLES ===\n" + "list coreTestElement\n" + "list sst.linear\n" + "list ariel miranda\n" + "list ariel miranda.ReverseSingleStreamGenerator\n"; + } + else if ( command == "find" ) { + text += "=== FIND ===\n" + "Search for text within component/subcomponent fields. " + "Displays all loaded components/subcomponents with the specified text.\n" + "Currently only supports searching within Description, ELI Version, Compile Date, and Category.\n\n" + "=== USAGE ===\n" + "- Find {field} [search term] \n\n" + "'field' - Component/subcomponent fields.\n" + "Valid keywords - [Description, Version, Compiledate, Category] (case-insensitive)\n" + "Search term can be multiple words, but is case-sensitive\n\n" + "=== EXAMPLES ===\n" + "find Description test\n" + "find compiledate Oct 17\n" + "find category UNCATEGORIZED\n"; + } + else { + text += "ERROR: Unknown command '" + command + "'\n\nUse the command 'Help' to see usage options."; + } + } + + // Parse commands + else { + // Get args + auto start = std::next(inputWords.begin(), 1); + auto end = inputWords.end(); + std::list args(start, end); + + if ( command == "list" ) { text += listLibraryInfo(args); } + + else if ( command == "find" ) { + text += findLibraryInfo(args); + } + } + + return text; +} + +int +addLibFilter(std::string libFilter, std::string componentFilter = "") +{ + for ( auto& library : g_libInfoArray ) { + if ( library.getLibraryName() == libFilter ) { + library.setLibraryFilter(true); + + if ( componentFilter != "" ) { library.setComponentFilter(componentFilter); } + return 0; + } + } + + // Error - library not found + return 1; +} + +std::string +listLibraryInfo(std::list args) +{ + std::stringstream outputStream; + + if ( args == std::list { "all" } ) { + outputStream << "\n-Displaying All Libraries-"; + for ( auto& library : g_libInfoArray ) { + library.resetFilters(true); + } + } + else { + for ( auto& library : g_libInfoArray ) { + library.resetFilters(false); + } + + outputStream << "\n-Displaying:"; + for ( std::string arg : args ) { + outputStream << " " + arg; + std::string library = ""; + std::string component = ""; + + // Parse library.component + size_t split = arg.find('.'); + if ( split == std::string::npos ) { library = arg; } + else { + library = arg.substr(0, split); + component = arg.substr(split + 1); + } + + // Check for invalid input + if ( addLibFilter(library, component) ) { + return "ERR - Could not find Library/Component '" + library + "." + component + "'"; + } + } + outputStream << "-"; + } + + for ( auto& library : g_libInfoArray ) { + library.outputText(outputStream); + } + + return outputStream.str(); +} + +std::string +findLibraryInfo(std::list args) +{ + std::stringstream outputStream; + + // Error handling + if ( args.size() < 2 ) { + return "Invalid input. Your command should be in the format of 'find {tag} {search term}'\n\n" + "For example, `Find Description Profiler`"; + } + + std::string inputTag = args.front(); + convertToLower(inputTag); + + // Compare to tag list and convert to proper string + auto mapIter = g_searchData.componentTags.find(inputTag); + + if ( mapIter != g_searchData.componentTags.end() ) { + std::string tag = mapIter->second; + + args.pop_front(); + std::string searchTerm = ""; + for ( std::string arg : args ) { + if ( arg == args.back() ) { searchTerm += arg; } + else { + searchTerm += arg + " "; + } + } + + // Search through libraries + for ( auto& library : g_libInfoArray ) { + library.resetFilters(false); + library.filterSearch(outputStream, tag, searchTerm); + library.outputText(outputStream); + } + return outputStream.str(); + } + + return "Invalid component tag. Choose from [Description, Version, Compiledate, Category]"; +} + +void +setInfoText(std::string infoString) +{ + std::vector stringVec; + g_textPos = 0; + + // Splits the string into individual lines and stores them into the infoText vector + std::stringstream infoStream(infoString); + std::string line; + + while ( std::getline(infoStream, line, '\n') ) { + stringVec.push_back((line + "\n")); + } + g_infoText.clear(); // clears memory + g_infoText = stringVec; +} +#endif + static void addELI(ElemLoader& loader, const std::string& lib, bool optional) { @@ -124,6 +389,11 @@ addELI(ElemLoader& loader, const std::string& lib, bool optional) } else if ( !optional ) { fprintf(stderr, "**** WARNING - UNABLE TO PROCESS LIBRARY = %s\n", lib.c_str()); + fprintf( + stderr, "**** CHECK: Has this library been registered with sst-core using the 'sst-register' utility?\n"); + fprintf(stderr, "**** CHECK: sst-info searches are case-sensitive\n"); + fprintf(stderr, "**** CHECK: Do not include the prefix or file extension when using the lib option.\n"); + fprintf(stderr, "**** EXAMPLE: 'sst-info -l PaintShop' to display model information from libPaintShop.so\n"); if ( g_configuration.debugEnabled() ) { std::cerr << err_sstr.str() << std::endl; } } else { @@ -152,25 +422,34 @@ processSSTElementFiles() addELI(loader, l, g_configuration.processAllElements()); } - // Do we output in Human Readable form - if ( g_configuration.getOptionBits() & CFG_OUTPUTHUMAN ) { outputSSTElementInfo(); } + // Store info strings for interactive mode + if ( g_configuration.interactiveEnabled() ) { + for ( size_t x = 0; x < g_libInfoArray.size(); x++ ) { + g_libInfoArray[x].setAllLibraryInfo(); + } + } + else { + // Do we output in Human Readable form + if ( g_configuration.getOptionBits() & CFG_OUTPUTHUMAN ) { outputSSTElementInfo(); } - // Do we output an XML File - if ( g_configuration.getOptionBits() & CFG_OUTPUTXML ) { generateXMLOutputFile(); } + // Do we output an XML File + if ( g_configuration.getOptionBits() & CFG_OUTPUTXML ) { generateXMLOutputFile(); } + } } void -generateXMLOutputFile() +outputSSTElementInfo() { - g_Outputter.outputXML(); + g_Outputter.outputHumanReadable(std::cout); } void -outputSSTElementInfo() +generateXMLOutputFile() { - g_Outputter.outputHumanReadable(std::cout); + g_Outputter.outputXML(); } + void OverallOutputter::outputHumanReadable(std::ostream& os) { @@ -265,6 +544,9 @@ SSTInfoConfig::SSTInfoConfig(bool suppress_print) : ConfigShared(suppress_print, DEF_FLAG("debug", 'd', "Enable debugging messages", std::bind(&SSTInfoConfig::setEnableDebug, this, _1)); DEF_FLAG( "nodisplay", 'n', "Do not display output [default: off]", std::bind(&SSTInfoConfig::setNoDisplay, this, _1)); + DEF_FLAG( + "interactive", 'i', "(EXPERIMENTAL) Enable interactive command line mode", + std::bind(&SSTInfoConfig::setInteractive, this, _1)); DEF_SECTION_HEADING("XML Options"); DEF_FLAG("xml", 'x', "Generate XML data [default:off]", std::bind(&SSTInfoConfig::setXML, this, _1)); DEF_ARG( @@ -312,76 +594,6 @@ SSTInfoConfig::outputUsage() cout << endl; } -#if 0 -void -SSTInfoConfig::outputVersion() -{ - cout << "SST Release Version " PACKAGE_VERSION << endl; -} - -int -SSTInfoConfig::parseCmdLine(int argc, char* argv[]) -{ - m_AppName = argv[0]; - - static const struct option longOpts[] = { { "help", no_argument, nullptr, 'h' }, - { "version", no_argument, nullptr, 'v' }, - { "debug", no_argument, nullptr, 'd' }, - { "nodisplay", no_argument, nullptr, 'n' }, - { "xml", no_argument, nullptr, 'x' }, - { "quiet", no_argument, nullptr, 'q' }, - { "outputxml", required_argument, nullptr, 'o' }, - { "libs", required_argument, nullptr, 'l' }, - { "elemenfilt", required_argument, nullptr, 0 }, - { nullptr, 0, nullptr, 0 } }; - while ( 1 ) { - int opt_idx = 0; - const int intC = getopt_long(argc, argv, "hvqdnxo:l:", longOpts, &opt_idx); - if ( intC == -1 ) break; - - const char c = static_cast(intC); - - switch ( c ) { - case 'h': - outputUsage(); - return 1; - case 'v': - outputVersion(); - return 1; - case 'q': - m_optionBits &= ~CFG_VERBOSE; - break; - case 'd': - m_debugEnabled = true; - break; - case 'n': - m_optionBits &= ~CFG_OUTPUTHUMAN; - break; - case 'x': - m_optionBits |= CFG_OUTPUTXML; - break; - case 'o': - m_XMLFilePath = optarg; - break; - case 'l': - { - addFilter(optarg); - break; - } - case 0: - if ( !strcmp(longOpts[opt_idx].name, "elemnfilt") ) { addFilter(optarg); } - break; - } - } - - while ( optind < argc ) { - addFilter(argv[optind++]); - } - - return 0; -} -#endif - void SSTInfoConfig::addFilter(const std::string& name_str) { @@ -417,6 +629,143 @@ shouldPrintElement(const std::string& libName, const std::string& elemName) return false; } +void +SSTLibraryInfo::setLibraryInfo(std::string baseName, std::string componentName, std::string info) +{ + ComponentInfo componentInfo; + std::map infoMap; + + // Split string into lines and map each key:value pair + std::stringstream infoStream(info); + std::string line; + while ( std::getline(infoStream, line, '\n') ) { + size_t split = line.find(':'); + + std::string tag; + std::string value; + + if ( split == std::string::npos ) { + tag = line; + value = ""; + } + else { + tag = line.substr(0, split); + value = line.substr(split + 1); + } + + infoMap.insert(make_pair(tag, value)); + componentInfo.stringIndexer.push_back(tag); + } + + componentInfo.componentName = componentName; + componentInfo.infoMap = infoMap; + + // Add to component lists + m_componentNames.push_back(componentName); + m_components[baseName].push_back(componentInfo); +} + +void +SSTLibraryInfo::outputText(std::stringstream& outputStream) +{ + if ( this->m_libraryFilter ) { + outputStream << "\n================================================================================\n"; + outputStream << "ELEMENT LIBRARY: " << this->m_name << endl; + + // Loop over component types + for ( auto& pair : this->m_components ) { + std::string componentType = pair.first; + outputStream << componentType << "s (" << pair.second.size() << " total)\n"; + + // Loop over each component + for ( int idx = 0; idx < int(pair.second.size()); idx++ ) { + auto component = pair.second[idx]; + + // Apply filter + bool filtered = + std::find(m_componentFilters.begin(), m_componentFilters.end(), component.componentName) != + m_componentFilters.end(); + if ( (m_componentFilters.size() == 0) || filtered ) { + outputStream << " " << componentType << " " << idx << ": " << component.componentName << endl; + + // Iterate through infoMap using the string indexer + for ( auto key : component.stringIndexer ) { + std::string val = component.infoMap[key]; + + if ( val == "" ) { outputStream << key << endl; } + else { + outputStream << key << ": " << val << endl; + } + } + outputStream << endl; + } + } + } + } +} + +void +SSTLibraryInfo::filterSearch(std::stringstream& outputStream, std::string tag, std::string searchTerm) +{ + int count = 0; + + for ( auto& pair : m_components ) { + for ( auto& component : pair.second ) { + std::string searchString = component.infoMap[tag]; + size_t found = searchString.find(searchTerm); + + // If term is found, set Library to show and add component to filters + if ( found != std::string::npos ) { + m_libraryFilter = true; + m_componentFilters.push_back(component.componentName); + count++; + } + } + } + + outputStream << "-Found " << count + << " components in " + m_name + " with '" + searchTerm + "' in '" + tag.substr(6) + "'-\n"; +} + +template +void +SSTLibraryInfo::setAllLibraryInfo() +{ + // lib is an InfoLibrary + auto* lib = ELI::InfoDatabase::getLibrary(getLibraryName()); + if ( lib ) { + // Only print if there is something of that type in the library + if ( lib->numEntries() != 0 ) { + // Create map keys based on type name + std::string baseName = std::string(BaseType::ELI_baseName()); + + // lib->getMap returns a map. BaseInfo is + // actually a Base::BuilderInfo and the implementation is in + // eli/elementinfo as BuilderInfoImpl + for ( auto& map : lib->getMap() ) { + std::stringstream infoStream; + map.second->toString(infoStream); + + setLibraryInfo(baseName, map.first, infoStream.str()); + } + } + } + else { + // os << "No " << BaseType::ELI_baseName() << "s\n"; + } +} + +void +SSTLibraryInfo::setAllLibraryInfo() +{ + + setAllLibraryInfo(); + setAllLibraryInfo(); + setAllLibraryInfo(); + setAllLibraryInfo(); + setAllLibraryInfo(); +} + template void SSTLibraryInfo::outputHumanReadable(std::ostream& os, bool printAll) @@ -499,3 +848,183 @@ SSTLibraryInfo::outputXML(int LibIndex, TiXmlNode* XMLParentElement) outputXML(XMLLibraryElement); XMLParentElement->LinkEndChild(XMLLibraryElement); } + +#ifdef HAVE_CURSES +void +InteractiveWindow::start() +{ + g_textPos = 0; + g_popupEnabled = false; + + initscr(); + cbreak(); + noecho(); + draw(); + setInfoText(parseInput("help")); + printInfo(); + + // Loop for input + getInput(); + endwin(); +} + +void +InteractiveWindow::getInput() +{ + std::string input = ""; + std::string output = ""; + std::string stashedInput = ""; + int entryIdx = -1; + + // Main loop for console input + while ( true ) { + int c = wgetch(console); + + // Parse entered text + if ( c == '\n' ) { + if ( input != "" ) { + g_prevInput.push_front(input); + output = parseInput(input); + setInfoText(output); + + g_window.draw(); + g_window.printInfo(); + input = ""; + entryIdx = -1; + } + } + // Autofill Box + // else if ( c == '\t' ) { + // g_window.toggleAutofillBox(); + // } + // Resizing the window + else if ( c == KEY_RESIZE ) { + g_window.draw(); + g_window.printInfo(); + } + // Handle backspaces + else if ( c == KEY_BACKSPACE ) { + int pos = g_window.getCursorPos(); + if ( pos > 1 ) { + g_window.printConsole("\b \b"); + input.pop_back(); + } + } + // Scrolling + else if ( c == KEY_UP ) { + if ( g_popupEnabled ) {} + else { + if ( g_textPos > 0 ) { + g_textPos -= 1; + g_window.printInfo(); + } + } + } + else if ( c == KEY_DOWN ) { + if ( g_popupEnabled ) {} + else { + if ( (int)g_textPos < (int)g_infoText.size() - (int)LINES + 3 ) { + g_textPos += 1; + g_window.printInfo(); + } + } + } + // Cycle through previous commands + else if ( c == KEY_PPAGE ) { + if ( entryIdx == -1 ) { stashedInput = input; } + + if ( entryIdx < int(g_prevInput.size() - 1) ) { + entryIdx++; + input = g_prevInput[entryIdx]; + + g_window.draw(); + g_window.printInfo(); + g_window.printConsole(input.c_str()); + } + } + else if ( c == KEY_NPAGE ) { + if ( entryIdx >= 0 ) { + entryIdx--; + if ( entryIdx == -1 ) { input = stashedInput; } + else { + input = g_prevInput[entryIdx]; + } + + g_window.draw(); + g_window.printInfo(); + g_window.printConsole(input.c_str()); + } + } + // Regular characters + else if ( c <= 255 ) { + input += c; + std::string letter(1, c); + g_window.printConsole(letter.c_str()); + } + + // Make sure the cursor resets to the correct place + g_window.resetCursor(input.size() + 1); + } +} + +void +InteractiveWindow::draw(bool drawConsole) +{ + werase(info); + delwin(info); + info = newwin(LINES - 3, COLS, 0, 0); + scrollok(info, true); + wrefresh(info); + + if ( drawConsole ) { + werase(console); + delwin(console); + console = newwin(3, COLS, LINES - 3, 0); + scrollok(console, false); + keypad(console, true); + box(console, 0, 0); + mvwprintw(console, 0, 1, " Console "); + wmove(console, 1, 1); + wrefresh(console); + } +} + +void +InteractiveWindow::toggleAutofillBox() +{ + // Toggle flag + g_popupEnabled = !g_popupEnabled; + + if ( g_popupEnabled ) { + int height = int(LINES / 3); + int width = int(COLS / 6); + int starty = LINES - height - 3; + int startx = getcurx(console) + 1; + autofillBox = newwin(height, width, starty, startx); + box(autofillBox, 0, 0); + wrefresh(autofillBox); + } + else { + werase(autofillBox); + draw(false); + printInfo(); + } +} + +void +InteractiveWindow::printInfo() +{ + unsigned int posMax = + ((int)g_infoText.size() < LINES - 3) ? g_textPos + g_infoText.size() : g_textPos + (LINES - 3); + + std::string infoString = ""; + for ( unsigned int i = g_textPos; i < posMax; i++ ) { + infoString += g_infoText[i]; + } + + wprintw(info, infoString.c_str()); + wrefresh(info); + wrefresh(console); // moves the cursor back into the console window + wmove(console, 1, 1); +} +#endif \ No newline at end of file diff --git a/src/sst/core/sstinfo.h b/src/sst/core/sstinfo.h index a63bb18eb..00494e3f0 100644 --- a/src/sst/core/sstinfo.h +++ b/src/sst/core/sstinfo.h @@ -53,6 +53,9 @@ class SSTInfoConfig : public ConfigShared return res; } + /** Clears the current filter map */ + void clearFilterMap() { m_filters.clear(); } + /** Return the filter map */ FilterMap_t& getFilterMap() { return m_filters; } @@ -66,13 +69,14 @@ class SSTInfoConfig : public ConfigShared bool debugEnabled() const { return m_debugEnabled; } bool processAllElements() const { return m_filters.empty(); } bool doVerbose() const { return m_optionBits & CFG_VERBOSE; } + bool interactiveEnabled() const { return m_interactive; } + void addFilter(const std::string& name); protected: std::string getUsagePrelude() override; private: void outputUsage(); - void addFilter(const std::string& name); int setPositionalArg(int UNUSED(num), const std::string& arg) { @@ -105,6 +109,12 @@ class SSTInfoConfig : public ConfigShared return 0; } + int setInteractive(const std::string& UNUSED(arg)) + { + m_interactive = true; + return 0; + } + int setXML(const std::string& UNUSED(arg)) { m_optionBits |= CFG_OUTPUTXML; @@ -135,6 +145,7 @@ class SSTInfoConfig : public ConfigShared unsigned int m_optionBits; std::string m_XMLFilePath; bool m_debugEnabled; + bool m_interactive; FilterMap_t m_filters; }; @@ -157,17 +168,37 @@ class SSTLibraryInfo // std::string getLibraryName() {if (m_eli && m_eli->name) return m_eli->name; else return ""; } std::string getLibraryName() { return m_name; } + /** Store all Library Information. */ + void setAllLibraryInfo(); + /** Output the Library Information. * @param LibIndex The Index of the Library. */ void outputHumanReadable(std::ostream& os, int LibIndex); /** Create the formatted XML data of the Library. - * @param LibIndex The Index of the Library. + * @param Index The Index of the Library. * @param XMLParentElement The parent element to receive the XML data. */ void outputXML(int Index, TiXmlNode* XMLParentElement); + /** Put text into info map*/ + void setLibraryInfo(std::string baseName, std::string componentName, std::string info); + + /** Return text from info map based on filters */ + void outputText(std::stringstream& os); + + /** Set filters based on search term */ + void filterSearch(std::stringstream& outputStream, std::string tag, std::string searchTerm); + + /** Filter output from info map*/ + bool getFilter() { return m_libraryFilter; } + void resetFilters(bool libFilter) { m_libraryFilter = libFilter, m_componentFilters.clear(); } + void setLibraryFilter(bool filter) { m_libraryFilter = filter; } + void setComponentFilter(std::string component) { m_componentFilters.push_back(component); } + + template + void setAllLibraryInfo(); template void outputHumanReadable(std::ostream& os, bool printAll); template @@ -176,9 +207,64 @@ class SSTLibraryInfo std::string getLibraryDescription() { return ""; } private: - std::string m_name; + // Contains info strings for each individual component, subcomponent, etc. + struct ComponentInfo + { + std::string componentName; + std::vector stringIndexer; // Used to maintain order of strings in infoMap + std::map infoMap; + }; + + // Stores all component info, keyed by their "BaseTypes" (component, subcomponent, module, etc.) + std::map> m_components; + std::vector m_componentNames; + bool m_libraryFilter = false; + std::vector m_componentFilters; + std::string m_name; +}; + +#ifdef HAVE_CURSES +#include +/** + * Handles all ncurses window operations for interactive SSTInfo. + */ +class InteractiveWindow +{ +public: + InteractiveWindow() + { + info = newwin(LINES - 3, COLS, 0, 0); + console = newwin(3, COLS, LINES - 3, 0); + } + + /* Draw/redraw info and console windows */ + void draw(bool drawConsole = true); + + /* Toggle display of the autofill box */ + void toggleAutofillBox(); + + /* Start up the interactive window */ + void start(); + + /* Main loop for user input */ + void getInput(); + + /* Prints SST-info text to the info window */ + void printInfo(); + + void printConsole(const char* input) { wprintw(console, input); } + void resetCursor(int pos) { wmove(console, 1, pos); } + int getCursorPos() { return getcurx(console); } + +private: + WINDOW* info; + WINDOW* console; + WINDOW* autofillBox; + bool autofillEnabled; }; +#endif } // namespace SST + #endif // SST_CORE_SST_INFO_H