Cross platform, Interactive text-based interface with command completion

℡╲_俬逩灬. 提交于 2019-12-04 07:01:44

In no particular order, (I've used none of them,) you should have a look at:

If none of those take your fancy you have one other possibility, and perhaps it may even be preferred. Write your backend as a daemon and have the frontend be a dumb program that communicates with the backend via any form of inter process communication. You can then use any GPLed library for your frontend without any problem as you can release the frontend as open source. Of course this will expose the communication protocol between frontend and backend, so you must be sure to be okay with that and of course the possibility that others may feel the need to make customizations to your frontend and perhaps even make their own. But assuming that your value is in the backend anyway, this should not pose a particular problem. And it may even be considered a plus, it would allow anyone with a bright idea to use your software in new and unexpected ways only increasing the popularity of your software.

Update: I haven't found a satisfactory (cross-platform) solution to the history/completion option, so I've ignored that for the time being, but I wanted to update this question with how I've implemented a simple interactive class. This interface won't be for mainstream user's but I'm finding it very handy for testing my code during implementation. Below is the initial implementation that might help others out (note that this code references methods and types I have not published, so won't compile out of the box)

Interact.h:

#ifndef INTERACT_H
#define INTERACT_H

class Interact;
class InteractCommand;
typedef  void (Interact::*PF_FUNC)(const InteractCommand &command, const StringVector &args);

struct InteractCommand
{
    const char *command;
    const char *argDesc;
    const char *desc;
    PF_FUNC func;
};

class Interact
{
private:
    static Log m_log;
    static InteractCommand m_commands[];
    static unsigned m_numCommands;

    bool m_stop;
    Database &m_database;
    StringVector &m_dirs;

public:
    Interact(Database &database, StringVector &dirs);
    ~Interact();

    /**
     * Main 'interact' loop.
     *
     * @return true if the loop exitted normally, else false if an error occurred.
     */
    bool interact();

private:
    // Functions
#define DEFFUNC(f) void FUNC_##f(const InteractCommand &command, const StringVector &args)
    DEFFUNC(database);
    DEFFUNC(dirs);
    DEFFUNC(exit);
    DEFFUNC(help);
#undef DEFFUNC


    /**
     * Print usage information for the specified command.
     *
     * @param command The command to print usage for.
     */
    static void usage(const InteractCommand &command);

    static void describeCommand(string &dest, const InteractCommand &command);
};

#endif // INTERACT_H

Interact.cpp:

#include "Interact.h"

Log Interact::m_log("Interact");

#define IFUNC(f) &Interact::FUNC_##f
InteractCommand Interact::m_commands[] = 
{
    { "database", "<file>|close", "Use database <file> or close opened database", IFUNC(database) },
    { "dirs", "dir[,dir...]", "Set the directories to scan", IFUNC(dirs) },
    { "exit", 0, "Exit", IFUNC(exit) },
    { "help", 0, "Print help", IFUNC(help) }
};
#undef IFUNC

unsigned Interact::m_numCommands = sizeof(m_commands) / sizeof(m_commands[0]);

Interact::Interact(MusicDatabase &database, StringVector &dirs) :
    m_stop(false),
    m_database(database),
    m_dirs(dirs)
{
}

Interact::~Interact()
{
}

bool Interact::interact()
{
    string line;
    StringVector args;
    unsigned i;

    m_stop = false;
    while (!m_stop)
    {
        args.clear();
        cout << "> ";
        if (!getline(cin, line) || cin.eof())
            break;
        else if (cin.fail())
            return false;

        if (!Util::splitString(line, " ", args) || args.size() == 0)
            continue;

        for (i = 0; i < m_numCommands; i++)
            if (strncasecmp(args[0].c_str(), m_commands[i].command, args[0].length()) == 0)
                break;
        if (i < m_numCommands)
            (this->*m_commands[i].func)(m_commands[i], args);
        else
            cout << "Unknown command '" << args[0] << "'" << endl;
    }
    return true;
}

void Interact::FUNC_database(const InteractCommand &command, const StringVector &args)
{
    if (args.size() != 2)
    {
        usage(command);
        return;
    }

    if (args[1] == "close")
    {
        if (m_database.opened())
            m_database.close();
        else
            cout << "Database is not open" << endl;
    }
    else
    {
        if (!m_database.open(args[1]))
        {
            cout << "Failed to open database" << endl;
        }
    }
}

void Interact::FUNC_dirs(const InteractCommand &command, const StringVector &args)
{
    if (args.size() == 1)
    {
        usage(command);
        return;
    }
    // TODO

}

void Interact::FUNC_exit(const InteractCommand &command, const StringVector &args)
{
    m_stop = true;
}

void Interact::FUNC_help(const InteractCommand &command, const StringVector &/*args*/)
{
    string descr;
    for (unsigned i = 0; i < m_numCommands; i++)
    {
        describeCommand(descr, m_commands[i]);
        cout << descr << endl;
    }
}

void Interact::usage(const InteractCommand &command)
{
    string descr;
    describeCommand(descr, command);
    cout << "usage: " << endl;
    cout << descr << endl;
}

void Interact::describeCommand(string &dest, const InteractCommand &command)
{
    dest.clear();
    string cmdStr = command.command;
    if (command.argDesc != 0)
    {
        cmdStr += " ";
        cmdStr += command.argDesc;
    }
    Util::format(dest, "  %-30s%s", cmdStr.c_str(), command.desc);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!