blob: 69418a9f317843f379521543853fca67c0efb3d1 [file] [log] [blame]
// 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;
}