Add response file support for command line options#121
Conversation
| @@ -2,6 +2,8 @@ | |||
|
|
|||
There was a problem hiding this comment.
Did you base/reference your implementation on the CRT implementation?
There was a problem hiding this comment.
Yeah, Ben sent me the original code.
|
|
||
| #include "impl/base.h" | ||
|
|
||
| using namespace std::experimental::filesystem; |
There was a problem hiding this comment.
Using directives in headers are not good design because it affects everyone including your header and can introduce namespace collisions.
|
|
||
| std::map<std::string_view, std::vector<std::string>> m_options; | ||
|
|
||
| template<typename O, typename L> |
There was a problem hiding this comment.
Number one coding guideline: be consistent! 😉
In this case, don't mix tabs and spaces.
| } | ||
|
|
||
| template <typename Character> | ||
| static void parse_command_line( |
There was a problem hiding this comment.
Where did you get this implementation? If it's basically a copy/paste of the CRT code that's fine it just needs to be cleaned up. Consistency.
There was a problem hiding this comment.
It's parse_command_line from argv_parsing.cpp, slightly modified. Sounds good.
| else if (svarg[0] == '@') | ||
| { | ||
| svarg.remove_prefix(1); | ||
| extract_respose_file(svarg, options); |
| std::map<std::string_view, std::vector<std::string>> m_options; | ||
|
|
||
| template<typename O, typename L> | ||
| void extract_option(std::string const& sarg, O const& options, L &last) |
| template<typename O, typename L> | ||
| void extract_option(std::string const& sarg, O const& options, L &last) | ||
| { | ||
| std::string_view svarg{ sarg }; |
There was a problem hiding this comment.
do you need svarg (or just use sarg directly)?
| std::map<std::string_view, std::vector<std::string>> m_options; | ||
|
|
||
| template<typename O, typename L> | ||
| void extract_option(std::string const& sarg, O const& options, L &last) |
There was a problem hiding this comment.
Just make the parameter a string_view and avoid the copies.
| template<typename O, typename L> | ||
| void extract_option(std::string const& sarg, O const& options, L &last) | ||
| { | ||
| std::string_view svarg{ sarg }; |
| } | ||
|
|
||
| template<typename O> | ||
| void extract_respose_file(std::string_view const& arg, O const& options) |
|
Tim, can we get some standalone unit test cases added under xlang/src/test/cpp/test/? Don't need to drive the exe, just the cmd_reader with expected exceptions for failure cases, etc. |
| void extract_respose_file(std::string_view const& arg, O const& options) | ||
| { | ||
| path response_path{ std::string{ arg } }; | ||
| std::wstring extension = response_path.extension(); |
There was a problem hiding this comment.
extension returns a path, not a wstring. This won't compile on Clang.
| m_options[last->name].push_back(std::string{ arg }); | ||
| } | ||
| } | ||
| for (C i = 1; i < argc; ++i) |
There was a problem hiding this comment.
The code still has white space issues all over the place. Find the "show white space" option in your editor and turn it on. Then change your editor settings to use a tab size of 4 and to insert tabs as spaces. Then cut and paste the entire source file and it should reset the white space consistently. Confirm visually.
| } | ||
| for (C i = 1; i < argc; ++i) | ||
| { | ||
| extract_option(std::string_view{ std::string{ argv[i] } }, options, last); |
There was a problem hiding this comment.
All of these conversions should be unnecessary as the extract_option function takes a string_view. You should be able to simply pass the argv[i] as the first argument.
| std::map<std::string_view, std::vector<std::string>> m_options; | ||
|
|
||
| template<typename O, typename L> | ||
| void extract_option(std::string_view arg, O const& options, L& last) |
There was a problem hiding this comment.
Consistency - use const& for string_view as that done elsewhere in this file.
There was a problem hiding this comment.
remove_prefix doesn't work with const& set. Should I change other methods to not be const& for consistency?
There was a problem hiding this comment.
Oh, I didn't see that the function modifies the parameter. In that case, this is appropriate and you can just leave it as is.
| std::experimental::filesystem::path response_path{ std::string{ arg } }; | ||
| std::string extension = response_path.extension().generic_string(); | ||
| std::transform(extension.begin(), extension.end(), extension.begin(), tolower); | ||
| if (is_directory(response_path) || extension == ".winmd") |
There was a problem hiding this comment.
@Scottj1s I see you originally added this check. Any particular reason? I'd rather not have code checking for file extensions since it is not particularly reliable. What if a developer accidentally passes a .exe file name? I believe ifstream will also skip over directories, so perhaps we should just drop this check altogether.
| auto last{ options.end() }; | ||
| for (auto i = 0; i < argc; i++) | ||
| { | ||
| extract_option(std::string_view{ argv[i] }, options, last); |
There was a problem hiding this comment.
This conversion should also be unnecessary.
| *argument_count = 0; | ||
|
|
||
| Character c; | ||
| std::string arg(""); |
There was a problem hiding this comment.
This constructor argument is unnecessary.
| Character c; | ||
| std::string arg(""); | ||
| int copy_character; // 1 = copy char to arg | ||
| unsigned numslash; // num of backslashes seen |
There was a problem hiding this comment.
This comment is redundant. Perhaps just call the variable backslash_count.
| { | ||
| *argument_count = 0; | ||
|
|
||
| Character c; |
There was a problem hiding this comment.
All the locals in this function should be initialized during declaration.
| in_quotes = false; | ||
| first_arg = true; | ||
|
|
||
| // Loop on each argument |
There was a problem hiding this comment.
Please remove comments that don't make the code substantially more understandable.
|
|
||
| first_arg = false; | ||
| } | ||
| } |
There was a problem hiding this comment.
This really needs some unit tests. 😉
698314e to
058d2ca
Compare
058d2ca to
b8acf63
Compare
|
|
||
| TEST_CASE("CmdReader") | ||
| { | ||
| const std::vector<option> options |
| std::experimental::filesystem::path response_path{ std::string{ arg } }; | ||
| std::string extension = response_path.extension().generic_string(); | ||
| std::transform(extension.begin(), extension.end(), extension.begin(), | ||
| [](unsigned char c) { return static_cast<unsigned char>(::tolower(c)); }); |
| { | ||
| const char* argv[] = { "progname", "@respfile.txt" }; | ||
| const size_t argc = 2; | ||
| std::ofstream resp_file; |
There was a problem hiding this comment.
consider making an raii type here to consolidate common logic and delete file in presence of an exception
There was a problem hiding this comment.
Might be simpler and more maintainable just to have an actual file in the test project containing the test arguments and not bother with generating and then deleting a file.
There was a problem hiding this comment.
I actually like the generation code - everything in one place
| // input and output | ||
| { | ||
| const char* argv[] = { "progname", "-in", "example_file.in", "-out", "example_file.out" }; | ||
| const size_t argc = 5; |
There was a problem hiding this comment.
all argc values can be = _countof(argv)
| std::ofstream resp_file; | ||
|
|
||
| resp_file.open("respfile.txt"); | ||
| resp_file << "-in example\\\"file.in -out example\\\"file.out"; |
| std::string extension = response_path.extension().generic_string(); | ||
| std::transform(extension.begin(), extension.end(), extension.begin(), | ||
| [](unsigned char c) { return static_cast<unsigned char>(::tolower(c)); }); | ||
| if (is_directory(response_path) || extension == ".winmd") |
There was a problem hiding this comment.
please add comment that this line is intended to help diagnose misuse of the '@' prefix (e.g., when supplying metadata files)
f751b80 to
8d2e326
Compare
8d2e326 to
c16ac11
Compare
|
Thanks Tim! |
Previous cppwinrt solution used CommandLineToArgvW to parse the response file, however this uses wide characters and is part of a Windows API, hence not cross-platform. Instead, the parsing mimics what crt does to get the command line to argv and argc. The rest of the logic is very similar to the previous implementation.