/* * Copyright (c) 2020, Itamar S. * Copyright (c) 2023, Shannon Booth * * SPDX-License-Identifier: BSD-2-Clause */ #include "Hunks.h" #include #include namespace Diff { Optional Parser::consume_unified_location() { auto consume_range = [this](Range& range) { if (!consume_line_number(range.start_line)) return false; if (consume_specific(',')) { if (!consume_line_number(range.number_of_lines)) return false; } else { range.number_of_lines = 1; } return true; }; if (!consume_specific("@@ -"sv)) return {}; HunkLocation location; if (!consume_range(location.old_range)) return {}; if (!consume_specific(" +"sv)) return {}; if (!consume_range(location.new_range)) return {}; if (!consume_specific(" @@"sv)) return {}; return location; } bool Parser::consume_line_number(size_t& number) { auto line = consume_while(is_ascii_digit); auto maybe_number = line.to_number(); if (!maybe_number.has_value()) return false; number = maybe_number.value(); return true; } ErrorOr Parser::parse_file_line(Optional const& strip_count) { // FIXME: handle parsing timestamps as well. auto line = consume_line(); GenericLexer line_parser(line); auto path = line_parser.consume_until('\t'); // No strip count given. Default to basename of file. if (!strip_count.has_value()) return String::from_byte_string(LexicalPath::basename(path)); // NOTE: We cannot use LexicalPath::parts as we want to strip the non-canonicalized path. auto const& parts = path.split_view('/'); // More components to strip than the filename has. Just pretend it is missing. if (strip_count.value() >= parts.size()) return String(); // Remove given number of leading components from the path. size_t components = parts.size() - strip_count.value(); StringBuilder stripped_path; for (size_t i = parts.size() - components; i < parts.size(); ++i) { TRY(stripped_path.try_append(parts[i])); if (i != parts.size() - 1) TRY(stripped_path.try_append("/"sv)); } return stripped_path.to_string(); } ErrorOr Parser::parse_patch(Optional const& strip_count) { Patch patch; patch.header = TRY(parse_header(strip_count)); patch.hunks = TRY(parse_hunks()); return patch; } ErrorOr
Parser::parse_header(Optional const& strip_count) { Header header; while (!is_eof()) { if (consume_specific("+++ "sv)) { header.new_file_path = TRY(parse_file_line(strip_count)); continue; } if (consume_specific("--- "sv)) { header.old_file_path = TRY(parse_file_line(strip_count)); continue; } if (next_is("@@ ")) { header.format = Format::Unified; return header; } consume_line(); } return header; } ErrorOr> Parser::parse_hunks() { Vector hunks; while (next_is("@@ ")) { // Try an locate a hunk location in this hunk. It may be prefixed with information. auto maybe_location = consume_unified_location(); consume_line(); if (!maybe_location.has_value()) break; Hunk hunk { *maybe_location, {} }; auto old_lines_expected = hunk.location.old_range.number_of_lines; auto new_lines_expected = hunk.location.new_range.number_of_lines; // We've found a location. Now parse out all of the expected content lines. while (old_lines_expected != 0 || new_lines_expected != 0) { StringView line = consume_line(); if (line.is_empty()) return Error::from_string_literal("Malformed empty content line in patch"); if (line[0] != ' ' && line[0] != '+' && line[0] != '-') return Error::from_string_literal("Invaid operation in patch"); auto const operation = Line::operation_from_symbol(line[0]); if (operation != Line::Operation::Removal) { if (new_lines_expected == 0) return Error::from_string_literal("Found more removal and context lines in patch than expected"); --new_lines_expected; } if (operation != Line::Operation::Addition) { if (old_lines_expected == 0) return Error::from_string_literal("Found more addition and context lines in patch than expected"); --old_lines_expected; } auto const content = line.substring_view(1, line.length() - 1); TRY(hunk.lines.try_append(Line { operation, TRY(String::from_utf8(content)) })); } TRY(hunks.try_append(hunk)); } if constexpr (HUNKS_DEBUG) { for (auto const& hunk : hunks) { dbgln("{}", hunk.location); for (auto const& line : hunk.lines) dbgln("{}", line); } } return hunks; } ErrorOr> parse_hunks(StringView diff) { Parser lexer(diff); while (!lexer.next_is("@@ ") && !lexer.is_eof()) lexer.consume_line(); return lexer.parse_hunks(); } }