| // 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 <unistd.h> // for isatty |
| |
| #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" |
| |
| 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 auto patchfile = args[0]; |
| std::string patch_contents; |
| { |
| const auto status = verible::file::GetContents(patchfile, &patch_contents); |
| if (!status.ok()) return status; |
| } |
| verible::PatchSet patch_set; |
| { |
| const auto status = patch_set.Parse(patch_contents); |
| if (!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 auto patchfile = args[0]; |
| std::string patch_contents; |
| { |
| const auto status = verible::file::GetContents(patchfile, &patch_contents); |
| if (!status.ok()) return status; |
| } |
| verible::PatchSet patch_set; |
| { |
| const auto status = patch_set.Parse(patch_contents); |
| if (!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 (isatty(0)) 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; |
| const auto status = verible::file::GetContents(arg, &contents); |
| if (!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; |
| } |