mu-regex: add match_groups, use everywhere
Add match_groups to Mu::Regex, and update old "raw" GRegex users.
This commit is contained in:
@ -28,6 +28,7 @@
|
|||||||
#include <regex>
|
#include <regex>
|
||||||
#include <utils/mu-utils.hh>
|
#include <utils/mu-utils.hh>
|
||||||
#include <utils/mu-utils-file.hh>
|
#include <utils/mu-utils-file.hh>
|
||||||
|
#include <utils/mu-regex.hh>
|
||||||
#include <utils/mu-error.hh>
|
#include <utils/mu-error.hh>
|
||||||
#include <utils/mu-option.hh>
|
#include <utils/mu-option.hh>
|
||||||
#include <utils/mu-lang-detector.hh>
|
#include <utils/mu-lang-detector.hh>
|
||||||
@ -673,26 +674,17 @@ doc_add_list_post(Document& doc, const MimeMessage& mime_msg)
|
|||||||
/* some mailing lists do not set the reply-to; see pull #1278. So for
|
/* some mailing lists do not set the reply-to; see pull #1278. So for
|
||||||
* those cases, check the List-Post address and use that instead */
|
* those cases, check the List-Post address and use that instead */
|
||||||
|
|
||||||
GMatchInfo* minfo{};
|
|
||||||
const auto list_post{mime_msg.header("List-Post")};
|
const auto list_post{mime_msg.header("List-Post")};
|
||||||
if (!list_post)
|
if (!list_post)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* compile the regex only once */
|
/* compile the regex only once */
|
||||||
static GRegex *rx = g_regex_new(
|
static const auto rx = unwrap(Regex::make(
|
||||||
"<?mailto:([a-z0-9!@#$%&'*+-/=?^_`{|}~]+)>?",
|
"<?mailto:([a-z0-9!@#$%&'*+-/=?^_`{|}~]+)>?", G_REGEX_CASELESS));
|
||||||
G_REGEX_CASELESS, (GRegexMatchFlags)0, {});
|
|
||||||
g_return_if_fail(rx);
|
|
||||||
|
|
||||||
Contacts contacts;
|
Contacts contacts;
|
||||||
if (g_regex_match(rx, list_post->c_str(), (GRegexMatchFlags)0, &minfo)) {
|
if (const auto groups{rx.match_groups(*list_post)}; groups)
|
||||||
auto address = (char*)g_match_info_fetch(minfo, 1);
|
contacts.push_back(Contact(groups->at(1)));
|
||||||
contacts.push_back(Contact(address));
|
|
||||||
g_free(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minfo)
|
|
||||||
g_match_info_free(minfo);
|
|
||||||
|
|
||||||
doc.add_extra_contacts(":list-post", contacts);
|
doc.add_extra_contacts(":list-post", contacts);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -402,9 +402,7 @@ message(const Regex& rx, size_t id)
|
|||||||
char buf[16];
|
char buf[16];
|
||||||
::snprintf(buf, sizeof(buf), "%zu", id);
|
::snprintf(buf, sizeof(buf), "%zu", id);
|
||||||
|
|
||||||
return to_string_gchar(
|
return unwrap(rx.replace(test_msg, buf));
|
||||||
g_regex_replace(rx, test_msg, -1, 0, buf,
|
|
||||||
G_REGEX_MATCH_DEFAULT, {}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TestData {
|
struct TestData {
|
||||||
|
|||||||
@ -31,6 +31,7 @@
|
|||||||
#include "utils/mu-utils.hh"
|
#include "utils/mu-utils.hh"
|
||||||
#include "utils/mu-utils-file.hh"
|
#include "utils/mu-utils-file.hh"
|
||||||
#include "utils/mu-result.hh"
|
#include "utils/mu-result.hh"
|
||||||
|
#include "utils/mu-regex.hh"
|
||||||
|
|
||||||
using namespace Mu;
|
using namespace Mu;
|
||||||
|
|
||||||
@ -135,7 +136,7 @@ test_maildir_mkdir_05(void)
|
|||||||
[[maybe_unused]] static void
|
[[maybe_unused]] static void
|
||||||
assert_matches_regexp(const char* str, const char* rx)
|
assert_matches_regexp(const char* str, const char* rx)
|
||||||
{
|
{
|
||||||
if (!g_regex_match_simple(rx, str, (GRegexCompileFlags)0, (GRegexMatchFlags)0)) {
|
if (const auto rex{Regex::make(rx)}; !rex || !rex->matches(str)) {
|
||||||
if (g_test_verbose())
|
if (g_test_verbose())
|
||||||
g_print("%s does not match %s", str, rx);
|
g_print("%s does not match %s", str, rx);
|
||||||
g_assert(0);
|
g_assert(0);
|
||||||
|
|||||||
@ -61,6 +61,69 @@ test_regex_match2()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
test_regex_match_groups()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto rx = Regex::make("^(\\d+)-(\\w+)$");
|
||||||
|
assert_valid_result(rx);
|
||||||
|
|
||||||
|
const auto groups{rx->match_groups("123-abc")};
|
||||||
|
g_assert_true(!!groups);
|
||||||
|
g_assert_cmpuint(groups->size(), ==, 3);
|
||||||
|
assert_equal(groups->at(0), "123-abc");
|
||||||
|
assert_equal(groups->at(1), "123");
|
||||||
|
assert_equal(groups->at(2), "abc");
|
||||||
|
|
||||||
|
// no match -> Nothing
|
||||||
|
g_assert_false(!!rx->match_groups("nope"));
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // always capture-count + 1 elements; a trailing group that
|
||||||
|
// did not participate is an empty string
|
||||||
|
auto rx = Regex::make("^(\\d+)(k|m|g)?$", G_REGEX_CASELESS);
|
||||||
|
assert_valid_result(rx);
|
||||||
|
|
||||||
|
const auto groups{rx->match_groups("512")};
|
||||||
|
g_assert_true(!!groups);
|
||||||
|
g_assert_cmpuint(groups->size(), ==, 3);
|
||||||
|
assert_equal(groups->at(1), "512");
|
||||||
|
assert_equal(groups->at(2), "");
|
||||||
|
|
||||||
|
const auto groups2{rx->match_groups("512K")};
|
||||||
|
g_assert_true(!!groups2);
|
||||||
|
g_assert_cmpuint(groups2->size(), ==, 3);
|
||||||
|
assert_equal(groups2->at(2), "K");
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // a non-participating group in the middle
|
||||||
|
auto rx = Regex::make("^(?:(a)|(b))(c)$");
|
||||||
|
assert_valid_result(rx);
|
||||||
|
|
||||||
|
const auto groups{rx->match_groups("bc")};
|
||||||
|
g_assert_true(!!groups);
|
||||||
|
g_assert_cmpuint(groups->size(), ==, 4);
|
||||||
|
assert_equal(groups->at(1), "");
|
||||||
|
assert_equal(groups->at(2), "b");
|
||||||
|
assert_equal(groups->at(3), "c");
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // no capture groups at all -> just the full match
|
||||||
|
auto rx = Regex::make("b.c");
|
||||||
|
assert_valid_result(rx);
|
||||||
|
|
||||||
|
const auto groups{rx->match_groups("abxcd")};
|
||||||
|
g_assert_true(!!groups);
|
||||||
|
g_assert_cmpuint(groups->size(), ==, 1);
|
||||||
|
assert_equal(groups->at(0), "bxc");
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // unset rx matches nothing
|
||||||
|
Regex rx;
|
||||||
|
g_assert_false(!!rx.match_groups("foo"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
test_regex_replace()
|
test_regex_replace()
|
||||||
{
|
{
|
||||||
@ -105,6 +168,7 @@ main(int argc, char* argv[])
|
|||||||
|
|
||||||
g_test_add_func("/regex/match", test_regex_match);
|
g_test_add_func("/regex/match", test_regex_match);
|
||||||
g_test_add_func("/regex/match2", test_regex_match2);
|
g_test_add_func("/regex/match2", test_regex_match2);
|
||||||
|
g_test_add_func("/regex/match-groups", test_regex_match_groups);
|
||||||
g_test_add_func("/regex/replace", test_regex_replace);
|
g_test_add_func("/regex/replace", test_regex_replace);
|
||||||
g_test_add_func("/regex/fail", test_regex_fail);
|
g_test_add_func("/regex/fail", test_regex_fail);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
** Copyright (C) 2022-2023 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
** Copyright (C) 2022-2026 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
**
|
**
|
||||||
** This program is free software; you can redistribute it and/or modify it
|
** This program is free software; you can redistribute it and/or modify it
|
||||||
** under the terms of the GNU General Public License as published by the
|
** under the terms of the GNU General Public License as published by the
|
||||||
@ -30,10 +30,6 @@ namespace Mu {
|
|||||||
* PCRE rather than std::regex because it is much faster.
|
* PCRE rather than std::regex because it is much faster.
|
||||||
*/
|
*/
|
||||||
struct Regex {
|
struct Regex {
|
||||||
#if !GLIB_CHECK_VERSION(2,74,0) /* backward compat */
|
|
||||||
#define G_REGEX_DEFAULT (static_cast<GRegexCompileFlags>(0))
|
|
||||||
#define G_REGEX_MATCH_DEFAULT (static_cast<GRegexMatchFlags>(0))
|
|
||||||
#endif
|
|
||||||
/**
|
/**
|
||||||
* Trivial constructor
|
* Trivial constructor
|
||||||
*
|
*
|
||||||
@ -144,6 +140,42 @@ struct Regex {
|
|||||||
// the str.c_str(). It *seems* like a false alarm.
|
// the str.c_str(). It *seems* like a false alarm.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match this regexp against the given string and return the captured
|
||||||
|
* groups.
|
||||||
|
*
|
||||||
|
* Element 0 is the full match, element n corresponds with the n-th
|
||||||
|
* capture group in the pattern. The result always has capture-count +
|
||||||
|
* 1 elements; groups that did not participate in the match are empty
|
||||||
|
* strings.
|
||||||
|
*
|
||||||
|
* @param str string to match
|
||||||
|
* @param mflags match flags
|
||||||
|
*
|
||||||
|
* @return the captured groups, or Nothing if there was no match (or no
|
||||||
|
* valid regexp)
|
||||||
|
*/
|
||||||
|
Option<std::vector<std::string>>
|
||||||
|
match_groups(const std::string& str,
|
||||||
|
GRegexMatchFlags mflags=G_REGEX_MATCH_DEFAULT) const {
|
||||||
|
GMatchInfo *minfo{};
|
||||||
|
if (!rx_ || !g_regex_match(rx_, str.c_str(), mflags, &minfo)) {
|
||||||
|
g_clear_pointer(&minfo, g_match_info_unref);
|
||||||
|
return Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> groups;
|
||||||
|
groups.reserve(g_regex_get_capture_count(rx_) + 1);
|
||||||
|
for (int i = 0; i != g_regex_get_capture_count(rx_) + 1; ++i) {
|
||||||
|
auto&& grp{g_match_info_fetch(minfo, i)};
|
||||||
|
groups.emplace_back(grp ? grp : "");
|
||||||
|
g_free(grp);
|
||||||
|
}
|
||||||
|
g_match_info_unref(minfo);
|
||||||
|
|
||||||
|
return Some(std::move(groups));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace all occurrences of @this regexp in some string with a
|
* Replace all occurrences of @this regexp in some string with a
|
||||||
* replacement string
|
* replacement string
|
||||||
|
|||||||
@ -44,6 +44,7 @@
|
|||||||
#include <glib/gprintf.h>
|
#include <glib/gprintf.h>
|
||||||
|
|
||||||
#include "mu-utils.hh"
|
#include "mu-utils.hh"
|
||||||
|
#include "mu-regex.hh"
|
||||||
#include "mu-unbroken.hh"
|
#include "mu-unbroken.hh"
|
||||||
|
|
||||||
#include "mu-error.hh"
|
#include "mu-error.hh"
|
||||||
@ -538,43 +539,31 @@ Mu::parse_date_time(const std::string& dstr, bool is_first, bool utc)
|
|||||||
Option<int64_t>
|
Option<int64_t>
|
||||||
Mu::parse_size(const std::string& val, bool is_first)
|
Mu::parse_size(const std::string& val, bool is_first)
|
||||||
{
|
{
|
||||||
int64_t size{-1};
|
|
||||||
std::string str;
|
|
||||||
GRegex* rx;
|
|
||||||
GMatchInfo* minfo;
|
|
||||||
|
|
||||||
/* one-sided ranges */
|
/* one-sided ranges */
|
||||||
if (val.empty())
|
if (val.empty())
|
||||||
return is_first ? 0 : std::numeric_limits<int64_t>::max();
|
return is_first ? 0 : std::numeric_limits<int64_t>::max();
|
||||||
|
|
||||||
rx = g_regex_new("^(\\d+)(b|k|kb|m|mb|g|gb)?$",
|
static const auto rx =
|
||||||
G_REGEX_CASELESS, (GRegexMatchFlags)0, NULL);
|
unwrap(Regex::make("^(\\d+)(b|k|kb|m|mb|g|gb)?$", G_REGEX_CASELESS));
|
||||||
minfo = NULL;
|
|
||||||
if (g_regex_match(rx, val.c_str(), (GRegexMatchFlags)0, &minfo)) {
|
|
||||||
|
|
||||||
char* s = g_match_info_fetch(minfo, 1);
|
const auto groups{rx.match_groups(val)};
|
||||||
size = atoll(s); // check overflow?
|
if (!groups)
|
||||||
g_free(s);
|
return Nothing;
|
||||||
|
|
||||||
s = g_match_info_fetch(minfo, 2);
|
int64_t size{::atoll(groups->at(1).c_str())}; // check overflow?
|
||||||
switch (s ? g_ascii_tolower(s[0]) : 0) {
|
|
||||||
case 'k': size *= 1024; break;
|
|
||||||
case 'm': size *= (1024 * 1024); break;
|
|
||||||
case 'g': size *= (1024 * 1024 * 1024); break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_free(s);
|
const auto& unit{groups->at(2)};
|
||||||
|
switch (unit.empty() ? 0 : g_ascii_tolower(unit.at(0))) {
|
||||||
|
case 'k': size *= 1024; break;
|
||||||
|
case 'm': size *= (1024 * 1024); break;
|
||||||
|
case 'g': size *= (1024 * 1024 * 1024); break;
|
||||||
|
default: break;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_regex_unref(rx);
|
|
||||||
g_match_info_unref(minfo);
|
|
||||||
|
|
||||||
if (size < 0)
|
if (size < 0)
|
||||||
return Nothing;
|
return Nothing;
|
||||||
else
|
else
|
||||||
return size;
|
return size;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string
|
||||||
|
|||||||
Reference in New Issue
Block a user