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

antonvw / wex / 24932513120

25 Apr 2026 01:57PM UTC coverage: 64.281% (-0.05%) from 64.326%
24932513120

push

github

web-flow
1202 allow comparing file using unified diff (#1232)

* first step, update the unified diff class to handle non git diffs

* when using diff as comparator add U0 flags, and changes for process::system

* factory::unified_diff now reports diff

* added ui item for unified diff opening for lists

* fixed path for unified diff: it is before the tab

* and added test for the paths

* changes after review

18754 of 32046 branches covered (58.52%)

Branch coverage included in aggregate %.

14955 of 20394 relevant lines covered (73.33%)

1457.93 hits per line

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

88.89
/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/core.h>
11
#include <wex/core/log.h>
12
#include <wex/factory/unified-diff.h>
13

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

16
namespace bp = boost::parser;
17

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

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

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

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

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

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

64
        index += 2;
50✔
65
      }
66

67
      m_diff->m_text.fill({});
25✔
68

69
      int i = 0;
25✔
70

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

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

91
        i++;
44✔
92
      }
93

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

99
      m_diff->report_diff();
25✔
100
      m_diff->trace("found");
75✔
101
    }
102
  };
37✔
103

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

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
  const std::string f("--- " + m_diff->token_from());
20✔
120
  const std::string t("\n+++ " + m_diff->token_to());
20✔
121

122
  auto const p_from = bp::lit(f.c_str());
20✔
123
  auto const p_to   = bp::lit(t.c_str());
20✔
124

125
  auto const parser_diff_lines =
126
    *(bp::eol >> bp::char_("-+ ") > *(bp::char_ - bp::eol - p_from - "\n@@"));
20✔
127

128
  auto const parser_hunk =
129
    bp::lit("\n@@") >
20✔
130
    bp::skip(bp::ws)
20✔
131
      [bp::repeat(2)[(bp::int_ >> ',' >> bp::int_) | bp::int_] >>
20✔
132
       bp::lit("@@")] > bp::omit[*(bp::char_ - bp::eol)] > parser_diff_lines;
60✔
133

134
  auto const parser_diff = p_from >> +(bp::char_ - p_to) >> p_to >>
20✔
135
                           +(bp::char_ - "\n@@") >> +parser_hunk;
20✔
136

137
  auto const parser_skip = bp::omit[*(bp::char_ - p_from)];
20✔
138

139
  auto const parser_all =
140
    *(parser_skip >> +parser_diff[action_diff]) > *bp::ws > bp::eoi[action_eoi];
20✔
141

142
  const bool res =
143
    m_diff->input().empty() || bp::parse(m_diff->input(), parser_all);
20✔
144

145
  if (!res)
20✔
146
  {
147
    log("unified_diff_parsing") << m_diff->input();
8✔
148
    log::status("Unified diff parsing failed");
8✔
149
  }
150

151
  return res;
20✔
152
}
20✔
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