Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
1 of 7 tasks
xparq opened this issue Nov 5, 2023 · 0 comments
Open
1 of 7 tasks

Comments

@xparq
Copy link
Owner

xparq commented Nov 5, 2023

  • 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.)
  • And then listvals() that's used in the tests could be added, too, as that could just the very 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!

@xparq xparq changed the title Perhaps another nice utility to also generate cmdlines, not just consume Perhaps another nice utility to also generate (not just consume) cmdlines Nov 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant