Skip to content

Perhaps another nice utility to also generate (not just consume) cmdlines #54

Description

@xparq
  • Just dump the current state... Well, and also quote args with spaces... and other scary chars...

    • Umm... Except map doesn't preserve order, so we're fucked with the named ones!... :-o (And no, ordered_map doesn't mean the order of insertion either!... :-/ )
    • Ummmm... And even if some rudimentary quoting is kinda easy, there's still the open-ended problem of command-lines being consumed by shells, so such a generator must actually speak the (quoting-globbing-escaping) language of a certain particular target shell that it prepares the command-line to!... A hard NO to that!
      • Mmm, but actually... ;) OK, well, just that some generic features which are mostly OK for most shells, or are a good baseline for further custom app-level processing, would still be nice.
        (A quoting example is done below, and some escaping callback lambda could also be added, too.)
        -> Also, wtime does something similar, and it's super handy. See its impl. -- with escaping for Win32::CreateProcess -- below, in another comment!
      • Also, BTW, the escaping function can be a callback!
        • ...at the cost of adding <functional> to the comp. burden! :-/
  • And then listvals() (already there in the tests) could be added, too, reusing the same mechanics. The tests use a stream as an output:

    auto listvals(auto const& container, const char* tail = "\n", const char* sep = ", ")
    {
        for (auto v = container.begin(); v != container.end(); ++v)
    	    cout << (v == container.begin() ? "":sep)
    	         << *v
    	         << (v+1 == container.end() ? tail:"");
    }
    

    but it could just as well write to a string (and an improved one could even be nice and precalc. its length first -- and an even nicer one that also does auto-quoting, or an even nicer one that does that with multi-char quotes... :) ):

    #include <string>
    #include <string_view>
    #include <cstring>
    #include <cassert>
    
    #define FOUND(expr) ((expr) != std::string::npos)
    #define CONTAINS(str, chars) FOUND((str).find_first_of(chars))
    string listvals(auto const& container, const char prewrap[] = "", const char postwrap[] = "", const char sep[] = ", ",
        const char* quote = "\"", // not const char[], to hint that it accepts nullptr!
        const char* scary_chars = " \t\n")
    // The pre/post wrapping are optional parts that only get written if not empty,
    // to support cases where callers would otherwise have to add an annoying
    // `if (container.empty())` or two themselves.
    {
        string result;
        if (!container.empty()) {
    	    size_t QLEN = quote ? strlen(quote) : 0;
    	    // Precalc. size... (Note: we're processing cmd args. We got time.)
    	    size_t size = strlen(prewrap) + (container.size() - 1) * strlen(sep) + strlen(postwrap);
    	    for (auto& v : container)
    		    size += v.length()
    			    + (quote && *quote && CONTAINS(v, scary_chars) ? // add quotes...
    				    (QLEN>1 ? QLEN:2) : 0); // special case for 1 (-> pair)!
    	    result.reserve(size);
    	    // Write...
    	    result += prewrap;
    	    for (auto v = container.begin(); v != container.end(); ++v) {
    		    if (quote && *quote && CONTAINS(*v, scary_chars))
    			    { result += string_view(quote, quote + (QLEN/2 ? QLEN/2 : 1)); // special case for 1 quote!
    			      result += *v;
    			      result += string_view(quote + QLEN/2); }
    		    else    { result += *v; }
    		    result += (v+1 == container.end() ? postwrap : sep);
    	    }
    //cout << "\n\n["<<result<<"]: " << "result.length() =? size: " << dec << result.length() << " vs. " << size << "\n\n";
    	    assert(result.length() == size);
        }
        return result;
    }
    #undef FOUND
    #undef CONTAINS
    
  • ...then the args "serializer" could be as simple as (well, but still needs to write to a string, as the other!):

    void dumpargs(Args& args, char prefixchar = '-', const char* longprefix = "--")
    {
        // Named...
        for (auto& [name, val] : args.named()) {
    	    if (name.length() == 1)
    		    cout << prefixchar << name << listvals(val, " ", "", " ");
    	    else
    		    cout << longprefix << name << listvals(val, "=", "", " ");
    	    cout << " ";
        }
        // Positional...
        cout << listvals(args.positional(), "", "", " ");
    }
    
  • Could be extra useful if the named/positional accessors would drop their (pretty orthodox) const (Drop const from named() and positional() (or have it both ways?) #55)! Then you could manipulate the arg set, and then "render" it to a new command line!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions