| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include <functional> | |
| #include <iostream> | |
| #include <string> | |
| #include "absl/flags/usage.h" | |
| #include "absl/status/status.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "common/strings/patch.h" | |
| #include "common/util/file_util.h" | |
| #include "common/util/init_command_line.h" | |
| #include "common/util/subcommand.h" | |
| #include "common/util/user_interaction.h" | |
| using verible::SubcommandArgsRange; | |
| using verible::SubcommandEntry; | |
| static absl::Status ChangedLines(const SubcommandArgsRange& args, | |
| std::istream& ins, std::ostream& outs, | |
| std::ostream& errs) { | |
| if (args.empty()) { | |
| return absl::InvalidArgumentError( | |
| "Missing patchfile argument. Use '-' for stdin."); | |
| } | |
| const absl::string_view patchfile = args[0]; | |
| std::string patch_contents; | |
| if (auto status = verible::file::GetContents(patchfile, &patch_contents); | |
| !status.ok()) { | |
| return status; | |
| } | |
| verible::PatchSet patch_set; | |
| if (auto status = patch_set.Parse(patch_contents); !status.ok()) { | |
| return status; | |
| } | |
| const verible::FileLineNumbersMap changed_lines( | |
| patch_set.AddedLinesMap(false)); | |
| for (const auto& file_lines : changed_lines) { | |
| outs << file_lines.first; | |
| if (!file_lines.second.empty()) { | |
| file_lines.second.FormatInclusive(outs << ' ', true); | |
| } | |
| outs << std::endl; | |
| } | |
| return absl::OkStatus(); | |
| } | |
| static absl::Status ApplyPick(const SubcommandArgsRange& args, | |
| std::istream& ins, std::ostream& outs, | |
| std::ostream& errs) { | |
| if (args.empty()) { | |
| return absl::InvalidArgumentError("Missing patchfile argument."); | |
| } | |
| const absl::string_view patchfile = args[0]; | |
| std::string patch_contents; | |
| if (auto status = verible::file::GetContents(patchfile, &patch_contents); | |
| !status.ok()) { | |
| return status; | |
| } | |
| verible::PatchSet patch_set; | |
| if (auto status = patch_set.Parse(patch_contents); !status.ok()) { | |
| return status; | |
| } | |
| return patch_set.PickApplyInPlace(ins, outs); | |
| } | |
| static absl::Status StdinTest(const SubcommandArgsRange& args, | |
| std::istream& ins, std::ostream& outs, | |
| std::ostream& errs) { | |
| constexpr size_t kOpenLimit = 10; | |
| outs << "This is a demo of re-opening std::cin, up to " << kOpenLimit | |
| << " times.\n" | |
| "Enter text when prompted.\n" | |
| "Ctrl-D sends an EOF to start the next file.\n" | |
| "Ctrl-C terminates the loop and exits the program." | |
| << std::endl; | |
| size_t file_count = 0; | |
| std::string line; | |
| for (; file_count < kOpenLimit; ++file_count) { | |
| outs << "==== file " << file_count << " ====" << std::endl; | |
| while (ins) { | |
| if (verible::IsInteractiveTerminalSession()) outs << "enter text: "; | |
| std::getline(ins, line); | |
| outs << "echo: " << line << std::endl; | |
| } | |
| if (ins.eof()) { | |
| outs << "EOF reached. Re-opening for next file" << std::endl; | |
| ins.clear(); // allows std::cin to read the next file | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| static absl::Status CatTest(const SubcommandArgsRange& args, std::istream& ins, | |
| std::ostream& outs, std::ostream& errs) { | |
| size_t file_count = 0; | |
| for (const auto& arg : args) { | |
| std::string contents; | |
| if (auto status = verible::file::GetContents(arg, &contents); | |
| !status.ok()) { | |
| return status; | |
| } | |
| outs << "<<<< contents of file[" << file_count << "] (" << arg << ") <<<<" | |
| << std::endl; | |
| outs << contents; | |
| outs << ">>>> end of file[" << file_count << "] (" << arg << ") >>>>" | |
| << std::endl; | |
| ++file_count; | |
| } | |
| return absl::OkStatus(); | |
| } | |
| static const std::pair<absl::string_view, SubcommandEntry> kCommands[] = { | |
| {"changed-lines", // | |
| {&ChangedLines, // | |
| R"(changed-lines patchfile | |
| Input: | |
| 'patchfile' is a unified-diff file from 'diff -u' or other version-controlled | |
| equivalents like {p4,git,hg,cvs,svn} diff. Use '-' to read from stdin. | |
| Output: (stdout) | |
| This prints output in the following format per line: | |
| filename [line-ranges] | |
| where line-ranges (optional) is suitable for tools that accept a set of lines | |
| to operate on, e.g. "1-4,8,21-42". | |
| line-ranges is omitted for files that are considered new in the patchfile. | |
| )"}}, | |
| // TODO(b/156530527): add options like -p for path pruning | |
| // TODO(fangism): Support '-' as patchfile. | |
| {"apply-pick", | |
| {&ApplyPick, | |
| R"(apply-pick patchfile | |
| Input: | |
| 'patchfile' is a unified-diff file from 'diff -u' or other version-controlled | |
| equivalents like {p4,git,hg,cvs,svn} diff. | |
| Effect: | |
| Modifies patched files in-place, following user selections on which patch | |
| hunks to apply. | |
| )"}}, | |
| {"stdin-test", // | |
| {&StdinTest, // | |
| R"(Test for re-opening stdin. | |
| This interactivel prompts the user to enter text, separating files with an EOF | |
| (Ctrl-D), and echoes the input text back to stdout. | |
| )", | |
| false}}, | |
| {"cat-test", // | |
| {&CatTest, // | |
| R"(Test for (UNIX) cat-like functionality. | |
| Usage: cat-test ARGS... | |
| where each ARG could point to a file on the filesystem or be '-' to read from stdin. | |
| Each '-' will prompt the user to enter text until EOF (Ctrl-D). | |
| Each 'file' echoed back to stdout will be enclosed in banner lines with | |
| <<<< and >>>>. | |
| )", | |
| false}}, | |
| }; | |
| int main(int argc, char* argv[]) { | |
| // Create a registry of subcommands (locally, rather than as a static global). | |
| verible::SubcommandRegistry commands; | |
| for (const auto& entry : kCommands) { | |
| const auto status = commands.RegisterCommand(entry.first, entry.second); | |
| if (!status.ok()) { | |
| std::cerr << status.message() << std::endl; | |
| return 2; // fatal error | |
| } | |
| } | |
| const std::string usage = absl::StrCat("usage: ", argv[0], | |
| " command args...\n" | |
| "available commands:\n", | |
| commands.ListCommands()); | |
| // Process invocation args. | |
| const auto args = verible::InitCommandLine(usage, &argc, &argv); | |
| if (args.size() == 1) { | |
| std::cerr << absl::ProgramUsageMessage() << std::endl; | |
| return 1; | |
| } | |
| // args[0] is the program name | |
| // args[1] is the subcommand | |
| // subcommand args start at [2] | |
| const SubcommandArgsRange command_args(args.cbegin() + 2, args.cend()); | |
| const auto& sub = commands.GetSubcommandEntry(args[1]); | |
| // Run the subcommand. | |
| const auto status = sub.main(command_args, std::cin, std::cout, std::cerr); | |
| if (!status.ok()) { | |
| std::cerr << status.message() << std::endl; | |
| return 1; | |
| } | |
| return 0; | |
| } |