• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

antonvw / wex / 22262718470

21 Feb 2026 07:15PM UTC coverage: 64.252% (-0.009%) from 64.261%
22262718470

Pull #1176

github

antonvw
old fix is no longer necessary
Pull Request #1176: 1173 improve parse unified diff

18541 of 31648 branches covered (58.59%)

Branch coverage included in aggregate %.

14764 of 20187 relevant lines covered (73.14%)

1516.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.4
/src/factory/unified-diff-parser.cpp
1
////////////////////////////////////////////////////////////////////////////////
2
// Name:      unified-diff-parser.cpp
3
// Purpose:   Implementation of unified_diff_parser
4
//            https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
5
// Author:    Anton van Wezenbeek
6
// Copyright: (c) 2025-2026 Anton van Wezenbeek
7
////////////////////////////////////////////////////////////////////////////////
8

9
#include <boost/parser/parser.hpp>
10
#include <wex/core/log.h>
11
#include <wex/factory/unified-diff.h>
12

13
#include "unified-diff-parser.h"
14

15
namespace bp = boost::parser;
16

17
wex::factory::unified_diff_parser::unified_diff_parser(unified_diff* diff)
14✔
18
  : m_diff(diff)
14✔
19
{
20
  m_diff->m_range.fill({0});
14✔
21
  m_diff->m_diffs    = 0;
14✔
22
  m_diff->m_is_first = true;
14✔
23
  m_diff->m_is_last  = true;
14✔
24
  m_diff->m_type     = unified_diff::diff_t::UNKNOWN;
14✔
25
}
14✔
26

27
bool wex::factory::unified_diff_parser::parse()
14✔
28
{
29
  auto const action_diff = [this](const auto& ctx)
14✔
30
  {
31
    const auto tpl    = _attr(ctx);
14✔
32
    m_diff->m_path[0] = wex::path(std::get<0>(tpl));
14✔
33
    m_diff->m_path[1] = wex::path(std::get<1>(tpl));
14✔
34
    m_diff->m_range.fill({0});
14✔
35

36
    for (const auto& hunk : std::get<2>(tpl))
34✔
37
    {
38
      int index = 0;
20✔
39

40
      m_diff->m_is_first = (hunk == *std::get<2>(tpl).begin());
20✔
41
      m_diff->m_is_last  = (hunk == std::get<2>(tpl).back());
20✔
42

43
      for (const auto& number : std::get<0>(hunk))
60✔
44
      {
45
        if (const auto* val = std::get_if<std::tuple<int, int>>(&number); val)
40✔
46
        {
47
          const int range            = std::get<1>(*val);
14✔
48
          m_diff->m_range[index]     = std::abs(std::get<0>(*val));
14✔
49
          m_diff->m_range[index + 1] = range;
14✔
50

51
          if (range > 0)
14✔
52
          {
53
            m_diff->m_diffs++;
4✔
54
          }
55
        }
56
        else
57
        {
58
          m_diff->m_range[index]     = std::abs(std::get<int>(number));
26✔
59
          m_diff->m_range[index + 1] = 1;
26✔
60
          m_diff->m_diffs++;
26✔
61
        }
62

63
        index += 2;
40✔
64
      }
65

66
      m_diff->m_text.fill({});
20✔
67

68
      int i = 0;
20✔
69

70
      for (const auto& line : std::get<1>(hunk))
48✔
71
      {
72
        log::trace("unified_diff hunk") << i << line;
28✔
73

74
        switch (line[0])
28!
75
        {
76
          case '+':
13✔
77
            m_diff->m_text[1].push_back(line.substr(1));
13✔
78
            break;
13✔
79
          case '-':
15✔
80
            m_diff->m_text[0].push_back(line.substr(1));
15✔
81
            break;
15✔
82
          case ' ':
×
83
            m_diff->m_text[0].push_back(line.substr(1));
×
84
            m_diff->m_text[1].push_back(line.substr(1));
×
85
            break;
×
86
          default:
×
87
            log("unified_diff unexpected hunk") << i << line;
×
88
        }
89

90
        i++;
28✔
91
      }
92

93
      m_diff->m_type =
20✔
94
        (m_diff->m_type == unified_diff::diff_t::UNKNOWN ?
20✔
95
           unified_diff::diff_t::FIRST :
96
           unified_diff::diff_t::OTHER);
97

98
      m_diff->report_diff();
20✔
99
      m_diff->trace("found");
60✔
100
    }
101
  };
28✔
102

103
  auto const action_eoi = [this](const auto& ctx)
10✔
104
  {
105
    m_diff->m_type = unified_diff::diff_t::LAST;
10✔
106
    m_diff->report_diff_finish();
10✔
107
    m_diff->trace("finish");
18✔
108
  };
23✔
109

110
  // (Skip the first lines)
111
  // The unified output format starts with a two-line header:
112
  // --- from-file from-file-modification-time
113
  // +++ to-file to-file-modification-time
114
  // Next come one or more hunks of differences:
115
  // @@ from-file-line-numbers to-file-line-numbers @@
116
  // line-from-either-file
117
  // line-from-either-file...
118

119
  auto const parser_diff_lines =
14✔
120
    *(bp::char_("-+ ") >
121
      bp::lexeme[*(bp::char_ - bp::eol - "\n--- a/" - "\n@@" - "\ndiff --")]);
122

123
  auto const parser_hunk =
124
    bp::lit("@@") > bp::repeat(2)[(bp::int_ >> ',' >> bp::int_) | bp::int_] >>
28✔
125
    bp::lit("@@") > bp::omit[bp::lexeme[+(bp::char_ - bp::eol)]] >
42✔
126
    parser_diff_lines;
28✔
127

128
  auto const parser_diff = bp::lit("--- a/") >> +(bp::char_ - "+++ b/") >>
14✔
129
                           bp::lit("+++ b/") >>
14✔
130
                           +(bp::char_ - "@@" - "diff --") >> +parser_hunk;
14✔
131

132
  auto const parser_skip =
14✔
133
    bp::omit[bp::lexeme[*(bp::char_ - (bp::eol >> "--- a/"))]];
134

135
  auto const parser_all =
136
    *(parser_skip >> +parser_diff[action_diff]) > bp::eoi[action_eoi];
14✔
137

138
  const bool res = bp::parse(m_diff->input(), parser_all, bp::ws);
14✔
139

140
  if (!res)
13✔
141
  {
142
    log("unified_diff_parsing") << m_diff->input();
8✔
143
    log::status("Unified diff parsing failed");
8✔
144
  }
145

146
  return res;
13✔
147
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc