| // 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. | |
| #ifndef VERIBLE_COMMON_STRINGS_PATCH_H_ | |
| #define VERIBLE_COMMON_STRINGS_PATCH_H_ | |
| #include <functional> | |
| #include <iosfwd> | |
| #include <map> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/string_view.h" | |
| #include "common/strings/compare.h" | |
| #include "common/strings/position.h" | |
| #include "common/util/container_iterator_range.h" | |
| #include "common/util/logging.h" | |
| namespace verible { | |
| namespace internal { | |
| // Forward declarations | |
| class FilePatch; | |
| // function interface like file::GetContents() | |
| using FileReaderFunction = std::function<absl::Status( | |
| absl::string_view filename, std::string* contents)>; | |
| // function interface like file::SetContents() | |
| using FileWriterFunction = std::function<absl::Status( | |
| absl::string_view filename, absl::string_view contents)>; | |
| } // namespace internal | |
| using FileLineNumbersMap = | |
| std::map<std::string, LineNumberSet, StringViewCompare>; | |
| // Collection of file changes. | |
| // Recursively inside these structs, we chose to copy std::string (owned memory) | |
| // instead of string_views into file contents memory kept separately. | |
| // This lets one safely modify patch structures. | |
| class PatchSet { | |
| public: | |
| PatchSet() = default; | |
| // Parse a unified-diff patch file into internal representation. | |
| absl::Status Parse(absl::string_view patch_contents); | |
| // Prints a unified-diff formatted output. | |
| std::ostream& Render(std::ostream& stream) const; | |
| // Returns a map<filename, line_numbers> that indicates which lines in each | |
| // file are new (in patch files, these are hunk lines starting with '+'). | |
| // New and existing files will always have entries in the returned map, | |
| // while deleted files will not. | |
| // If `new_file_ranges` is true, provide the full range of lines for new | |
| // files, otherwise leave their corresponding LineNumberSets empty. | |
| FileLineNumbersMap AddedLinesMap(bool new_file_ranges) const; | |
| // Interactively prompt user to select hunks to apply in-place. | |
| // 'ins' is the stream from which user-input is read, | |
| // and 'outs' is the stream that displays text and prompts to the user. | |
| absl::Status PickApplyInPlace(std::istream& ins, std::ostream& outs) const; | |
| protected: | |
| // For testing, allow mocking out of file I/O. | |
| absl::Status PickApply(std::istream& ins, std::ostream& outs, | |
| const internal::FileReaderFunction& file_reader, | |
| const internal::FileWriterFunction& file_writer) const; | |
| private: | |
| // Non-patch plain text that could describe the origins of the diff/patch, | |
| // e.g. from git-format-patch. | |
| std::vector<std::string> metadata_; | |
| // Collection of file differences. | |
| // Any metadata for the entire patch set will be lumped into the first file's | |
| // metadata. | |
| std::vector<internal::FilePatch> file_patches_; | |
| }; | |
| std::ostream& operator<<(std::ostream&, const PatchSet&); | |
| // Private implementation details follow. | |
| namespace internal { | |
| using LineIterator = std::vector<absl::string_view>::const_iterator; | |
| using LineRange = container_iterator_range<LineIterator>; | |
| // A single line of a patch hunk. | |
| struct MarkedLine { | |
| std::string line; | |
| MarkedLine() = default; | |
| // only used in manual test case construction | |
| // so ok to use CHECK here. | |
| explicit MarkedLine(absl::string_view text) : line(text.begin(), text.end()) { | |
| CHECK(!line.empty()) << "MarkedLine must start with a marker in [ -+]."; | |
| CHECK(Valid()) << "Unexpected marker '" << Marker() << "'."; | |
| } | |
| bool Valid() const { | |
| const char m = Marker(); | |
| return m == ' ' || m == '-' || m == '+'; | |
| } | |
| // The first column denotes whether a line is: | |
| // ' ': common context | |
| // '-': only in the left/old file | |
| // '+': only in the right/new file | |
| char Marker() const { return line[0]; } | |
| bool IsCommon() const { return Marker() == ' '; } | |
| bool IsAdded() const { return Marker() == '+'; } | |
| bool IsDeleted() const { return Marker() == '-'; } | |
| absl::string_view Text() const { | |
| return absl::string_view(&line[1], line.length() - 1); | |
| } | |
| // default equality operator | |
| bool operator==(const MarkedLine& other) const { return line == other.line; } | |
| bool operator!=(const MarkedLine& other) const { return !(*this == other); } | |
| absl::Status Parse(absl::string_view); | |
| }; | |
| std::ostream& operator<<(std::ostream&, const MarkedLine&); | |
| struct HunkIndices { | |
| int start; // starting line number for a hunk, 1-based | |
| int count; // number of lines to expect in this hunk | |
| // default equality operator | |
| bool operator==(const HunkIndices& other) const { | |
| return start == other.start && count == other.count; | |
| } | |
| bool operator!=(const HunkIndices& other) const { return !(*this == other); } | |
| absl::Status Parse(absl::string_view); | |
| }; | |
| std::ostream& operator<<(std::ostream&, const HunkIndices&); | |
| struct HunkHeader { | |
| HunkIndices old_range; | |
| HunkIndices new_range; | |
| // Some diff tools also include context such as the function or class | |
| // declaration that encloses this hunk. This is optional metadata. | |
| std::string context; | |
| // default equality operator | |
| bool operator==(const HunkHeader& other) const { | |
| return old_range == other.old_range && new_range == other.new_range && | |
| context == other.context; | |
| } | |
| bool operator!=(const HunkHeader& other) const { return !(*this == other); } | |
| absl::Status Parse(absl::string_view); | |
| }; | |
| std::ostream& operator<<(std::ostream&, const HunkHeader&); | |
| // One unit of a file change. | |
| class Hunk { | |
| public: | |
| Hunk() = default; | |
| Hunk(const Hunk&) = default; | |
| template <typename Iter> | |
| Hunk(int old_starting_line, int new_starting_line, Iter begin, Iter end) | |
| : header_{.old_range = {old_starting_line, 0}, | |
| .new_range = {new_starting_line, 0}}, | |
| lines_(begin, end) /* copy */ { | |
| UpdateHeader(); // automatically count marked lines | |
| } | |
| // Hunk is valid if its header's line counts are consistent with the set of | |
| // MarkedLines. | |
| absl::Status IsValid() const; | |
| const HunkHeader& Header() const { return header_; } | |
| const std::vector<MarkedLine>& MarkedLines() const { return lines_; } | |
| // If a hunk is modified for any reason, the number of added/removed lines may | |
| // have changed, so this will update the .count values. | |
| void UpdateHeader(); | |
| // Returns a set of line numbers for lines that are changed or new. | |
| LineNumberSet AddedLines() const; | |
| absl::Status Parse(const LineRange&); | |
| std::ostream& Print(std::ostream&) const; | |
| // Verify consistency of lines in the patch (old-file) against the file that | |
| // is read in whole. | |
| absl::Status VerifyAgainstOriginalLines( | |
| const std::vector<absl::string_view>& original_lines) const; | |
| // default structural comparison | |
| bool operator==(const Hunk& other) const { | |
| return header_ == other.header_ && lines_ == other.lines_; | |
| } | |
| bool operator!=(const Hunk& other) const { return !(*this == other); } | |
| // Splits this Hunk into smaller ones at the points of common context. | |
| // Returns array of hunks. If this hunk can no longer be split, then it is | |
| // returned as a singleton in the array. | |
| std::vector<Hunk> Split() const; | |
| private: | |
| // The header describes how many of each type of edit lines to expect. | |
| HunkHeader header_; | |
| // Sequence of edit lines (common, old, new). | |
| std::vector<MarkedLine> lines_; | |
| }; | |
| std::ostream& operator<<(std::ostream&, const Hunk&); | |
| struct SourceInfo { | |
| std::string path; // location to patched file, absolute or relative | |
| std::string timestamp; // unspecified date format, not parsed, optional | |
| absl::Status Parse(absl::string_view); | |
| }; | |
| std::ostream& operator<<(std::ostream&, const SourceInfo&); | |
| // Set of changes for a single file. | |
| class FilePatch { | |
| public: | |
| // Initialize data struct from a range of patch lines. | |
| absl::Status Parse(const LineRange&); | |
| // Returns true if this file is new. | |
| bool IsNewFile() const; | |
| // Returns true if this file is deleted. | |
| bool IsDeletedFile() const; | |
| const SourceInfo& NewFileInfo() const { return new_file_; } | |
| // Returns a set of line numbers for lines that are changed or new. | |
| LineNumberSet AddedLines() const; | |
| std::ostream& Print(std::ostream&) const; | |
| // Verify consistency of lines in the patch (old-file) against the file that | |
| // is read in whole. | |
| absl::Status VerifyAgainstOriginalLines( | |
| const std::vector<absl::string_view>& original_lines) const; | |
| absl::Status PickApplyInPlace(std::istream& ins, std::ostream& outs) const; | |
| // For testing with mocked-out file I/O. | |
| // Public to allow use by PatchSet::PickApply(). | |
| absl::Status PickApply(std::istream& ins, std::ostream& outs, | |
| const FileReaderFunction& file_reader, | |
| const FileWriterFunction& file_writer) const; | |
| private: | |
| // These are lines of informational text only, such as how the diff was | |
| // generated. They do not impact 'patch' behavior. | |
| std::vector<std::string> metadata_; | |
| SourceInfo old_file_; | |
| SourceInfo new_file_; | |
| std::vector<Hunk> hunks_; | |
| }; | |
| std::ostream& operator<<(std::ostream&, const FilePatch&); | |
| } // namespace internal | |
| } // namespace verible | |
| #endif // VERIBLE_COMMON_STRINGS_PATCH_H_ |