vAmiga icon indicating copy to clipboard operation
vAmiga copied to clipboard

RetroShell needs a new argument parser

Open dirkwhoffmann opened this issue 6 months ago • 3 comments

To implement decent file system commands, RetroShell needs a more flexible parser. E.g., I want to support commands with the following example syntax:

fs create c=<count> h=<count> s=<count> [name=<string>] type [-h] <filename>

In other words, I want to support key-value pairs and boolean flags.

Also, the new shell should print a small help message for each argument when pressing TAB twice, so we don't need external documentation for all commands.

dirkwhoffmann avatar Jun 12 '25 13:06 dirkwhoffmann

Boolean flags and argument descriptions are working. As a showcase, I've implemented a find command in the new navigator shell:

Image

Interestingly, by smartly juggling around with C++ lambdas for filtering and formatting, the code for the entire find command is just a few lines:

   root.add({

        .tokens = { "find" },
        .argx   = {
            { .name = { "name", "Search pattern" } },
            { .name = { "path", "Directory to search in" }, .flags = arg::opt },
            { .name = { "d", "Find directories only" }, .flags = arg::flag },
            { .name = { "f", "Find files only" }, .flags = arg::flag },
            { .name = { "r", "Search subdirectories, too" }, .flags = arg::flag },
            { .name = { "s", "Sort output" }, .flags = arg::flag } },
        .help   = { "Find files or directories." },
        .func   = [this] (Arguments& argv, const ParsedArguments &args, const std::vector<isize> &values) {

            auto pattern = FSPattern(args.at("name"));
            auto path = FSString(args, "path", ".");
            auto d = args.contains("d");
            auto f = args.contains("f");
            auto r = args.contains("r");
            auto s = args.contains("s");

            auto dir = fs.pwd().seek(path);

            FSOpt opt = {

                .sort = s,
                .recursive = r,

                .filter = [&](const FSPath &item) {

                    return pattern.match(item.last()) &&
                    (!d || item.isDirectory()) &&
                    (!f || item.isFile());
                },

                .formatter = [&](const FSPath &item) {

                    std::stringstream ss;
                    ss << item.name() + (item.isDirectory() ? " (dir)" : "");
                    return ss.str();
                }
            };

            std::vector<FSPath> matches;
            fs.collect(dir, matches, opt);
            for (auto &it : matches) { *this << opt.formatter(it) << '\n'; }
        }
    });

dirkwhoffmann avatar Jun 14 '25 08:06 dirkwhoffmann

that’s a lot of juggling lol

GINNOV avatar Jun 14 '25 17:06 GINNOV

The FileSystem class had code for printing debug information about block contents to stdout. I've prettified the output and made it available in the navigator via the block command:

Image

Hex dumps can be generated via the -h flag:

Image

The command handler is again very simple:

    root.add({

        .tokens = { "block", "" },
        .argx   = {
            { .name = { "nr", "Block number" }, .flags = arg::opt },
            { .name = { "h", "Print a hex dump" }, .flags = arg::flag },
        },
        .help   = { "Analyze a block" },
        .func   = [this] (Arguments& argv, const ParsedArguments &args, const std::vector<isize> &values) {

            auto nr = args.contains("nr") ? parseBlock(args.at("nr")) : fs.pwd().ref;

            if (auto ptr = fs.blockPtr(nr)) {

                std::stringstream ss;

                if (args.contains("h")) {
                    ptr->hexDump(ss);
                } else {
                    ptr->dump(ss);
                }

                *this << '\n' << ss << '\n';
            }
        }
    });

dirkwhoffmann avatar Jun 15 '25 14:06 dirkwhoffmann

New argument parser is in place.

dirkwhoffmann avatar Jun 21 '25 08:06 dirkwhoffmann