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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

0.0
/src/regex101.client.cc
1
/**
2
 * Copyright (c) 2022, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29

30
#include <filesystem>
31

32
#include "regex101.client.hh"
33

34
#include <curl/curl.h>
35

36
#include "base/itertools.hh"
37
#include "config.h"
38
#include "curl_looper.hh"
39
#include "yajlpp/yajlpp_def.hh"
40

41
namespace regex101::client {
42

43
static const typed_json_path_container<entry>&
UNCOV
44
get_entry_handlers()
×
45
{
46
    static constexpr json_path_handler_base::enum_value_t CRITERIA_ENUM[] = {
47
        {"DOES_MATCH"_frag, unit_test::criteria::DOES_MATCH},
48
        {"DOES_NOT_MATCH"_frag, unit_test::criteria::DOES_NOT_MATCH},
49

50
        json_path_handler_base::ENUM_TERMINATOR,
51
    };
52

53
    static const json_path_container UNIT_TEST_HANDLERS = {
UNCOV
54
        yajlpp::property_handler("description")
×
UNCOV
55
            .for_field(&unit_test::ut_description),
×
UNCOV
56
        yajlpp::property_handler("testString")
×
UNCOV
57
            .for_field(&unit_test::ut_test_string),
×
UNCOV
58
        yajlpp::property_handler("target").for_field(&unit_test::ut_target),
×
UNCOV
59
        yajlpp::property_handler("criteria")
×
UNCOV
60
            .with_enum_values(CRITERIA_ENUM)
×
UNCOV
61
            .for_field(&unit_test::ut_criteria),
×
62
    };
63

64
    static const typed_json_path_container<entry> retval = {
UNCOV
65
        yajlpp::property_handler("dateCreated")
×
UNCOV
66
            .for_field(&entry::e_date_created),
×
UNCOV
67
        yajlpp::property_handler("regex").for_field(&entry::e_regex),
×
UNCOV
68
        yajlpp::property_handler("testString").for_field(&entry::e_test_string),
×
UNCOV
69
        yajlpp::property_handler("flags").for_field(&entry::e_flags),
×
UNCOV
70
        yajlpp::property_handler("delimiter").for_field(&entry::e_delimiter),
×
UNCOV
71
        yajlpp::property_handler("flavor").for_field(&entry::e_flavor),
×
UNCOV
72
        yajlpp::property_handler("unitTests#")
×
UNCOV
73
            .for_field(&entry::e_unit_tests)
×
UNCOV
74
            .with_children(UNIT_TEST_HANDLERS),
×
UNCOV
75
        yajlpp::property_handler("permalinkFragment")
×
UNCOV
76
            .for_field(&entry::e_permalink_fragment),
×
77
    };
78

UNCOV
79
    return retval;
×
80
}
81

82
static const typed_json_path_container<upsert_response>&
UNCOV
83
get_response_handlers()
×
84
{
85
    static const typed_json_path_container<upsert_response> retval = {
UNCOV
86
        yajlpp::property_handler("deleteCode")
×
UNCOV
87
            .for_field(&upsert_response::cr_delete_code),
×
88
        yajlpp::property_handler("permalinkFragment")
×
UNCOV
89
            .for_field(&upsert_response::cr_permalink_fragment),
×
90
        yajlpp::property_handler("version").for_field(
×
91
            &upsert_response::cr_version),
92
    };
93

94
    return retval;
×
95
}
96

97
static const std::filesystem::path REGEX101_BASE_URL
98
    = "https://regex101.com/api/regex";
99
static const char* USER_AGENT = "lnav/" PACKAGE_VERSION;
100

101
Result<upsert_response, lnav::console::user_message>
102
upsert(entry& en)
×
103
{
104
    auto entry_json = get_entry_handlers().to_string(en);
×
105

106
    curl_request cr(REGEX101_BASE_URL.string());
×
107

108
    curl_easy_setopt(cr, CURLOPT_URL, cr.get_name().c_str());
×
109
    curl_easy_setopt(cr, CURLOPT_POST, 1);
×
UNCOV
110
    curl_easy_setopt(cr, CURLOPT_POSTFIELDS, entry_json.c_str());
×
111
    curl_easy_setopt(cr, CURLOPT_POSTFIELDSIZE, entry_json.size());
×
UNCOV
112
    curl_easy_setopt(cr, CURLOPT_USERAGENT, USER_AGENT);
×
113

114
    auto_mem<curl_slist> list(curl_slist_free_all);
×
115

116
    list = curl_slist_append(list, "Content-Type: application/json");
×
117

UNCOV
118
    curl_easy_setopt(cr, CURLOPT_HTTPHEADER, list.in());
×
119

120
    auto perform_res = cr.perform();
×
121
    if (perform_res.isErr()) {
×
122
        return Err(
×
123
            lnav::console::user_message::error(
×
124
                "unable to create entry on regex101.com")
UNCOV
125
                .with_reason(curl_easy_strerror(perform_res.unwrapErr())));
×
126
    }
127

128
    auto response = perform_res.unwrap();
×
129
    auto resp_code = cr.get_response_code();
×
130
    if (resp_code != 200) {
×
131
        return Err(lnav::console::user_message::error(
×
132
                       "unable to create entry on regex101.com")
UNCOV
133
                       .with_reason(attr_line_t()
×
UNCOV
134
                                        .append("received response code ")
×
135
                                        .append(lnav::roles::number(
×
136
                                            fmt::to_string(resp_code)))
×
UNCOV
137
                                        .append(" content ")
×
138
                                        .append_quoted(response)));
×
139
    }
140

UNCOV
141
    auto parse_res = get_response_handlers()
×
UNCOV
142
                         .parser_for(intern_string::lookup(cr.get_name()))
×
UNCOV
143
                         .with_ignore_unused(true)
×
UNCOV
144
                         .of(response);
×
UNCOV
145
    if (parse_res.isOk()) {
×
UNCOV
146
        return Ok(parse_res.unwrap());
×
147
    }
148

UNCOV
149
    auto errors = parse_res.unwrapErr();
×
UNCOV
150
    return Err(lnav::console::user_message::error(
×
151
                   "unable to create entry on regex101.com")
UNCOV
152
                   .with_reason(errors[0].to_attr_line({})));
×
153
}
154

155
struct retrieve_entity {
156
    std::string re_permalink_fragment;
157
    std::vector<int32_t> re_versions;
158
};
159

160
static const typed_json_path_container<retrieve_entity> RETRIEVE_ENTITY_HANDLERS
161
    = {
162
        yajlpp::property_handler("permalinkFragment")
163
            .for_field(&retrieve_entity::re_permalink_fragment),
164
        yajlpp::property_handler("versions#")
165
            .for_field(&retrieve_entity::re_versions),
166
};
167

168
retrieve_result_t
169
retrieve(const std::string& permalink)
×
170
{
UNCOV
171
    auto entry_url = REGEX101_BASE_URL / permalink;
×
172
    curl_request entry_req(entry_url.string());
×
173

174
    curl_easy_setopt(entry_req, CURLOPT_URL, entry_req.get_name().c_str());
×
175
    curl_easy_setopt(entry_req, CURLOPT_USERAGENT, USER_AGENT);
×
176

177
    auto perform_res = entry_req.perform();
×
178
    if (perform_res.isErr()) {
×
179
        return lnav::console::user_message::error(
×
180
                   attr_line_t("unable to get entry ")
×
181
                       .append_quoted(lnav::roles::symbol(permalink))
×
UNCOV
182
                       .append(" on regex101.com"))
×
183
            .with_reason(curl_easy_strerror(perform_res.unwrapErr()));
×
184
    }
185

186
    auto response = perform_res.unwrap();
×
187
    auto resp_code = entry_req.get_response_code();
×
UNCOV
188
    if (resp_code == 404) {
×
UNCOV
189
        return no_entry{};
×
190
    }
UNCOV
191
    if (resp_code != 200) {
×
192
        return lnav::console::user_message::error(
×
193
                   attr_line_t("unable to get entry ")
×
194
                       .append_quoted(lnav::roles::symbol(permalink))
×
UNCOV
195
                       .append(" on regex101.com"))
×
196
            .with_reason(
197
                attr_line_t()
×
UNCOV
198
                    .append("received response code ")
×
199
                    .append(lnav::roles::number(fmt::to_string(resp_code)))
×
200
                    .append(" content ")
×
201
                    .append_quoted(response));
×
202
    }
203

204
    auto parse_res
205
        = RETRIEVE_ENTITY_HANDLERS
206
              .parser_for(intern_string::lookup(entry_req.get_name()))
×
UNCOV
207
              .with_ignore_unused(true)
×
208
              .of(response);
×
209

210
    if (parse_res.isErr()) {
×
UNCOV
211
        auto parse_errors = parse_res.unwrapErr();
×
212

213
        return lnav::console::user_message::error(
×
214
                   attr_line_t("unable to get entry ")
×
UNCOV
215
                       .append_quoted(lnav::roles::symbol(permalink))
×
216
                       .append(" on regex101.com"))
×
217
            .with_reason(parse_errors[0].to_attr_line({}));
×
218
    }
219

220
    auto entry_value = parse_res.unwrap();
×
221

222
    auto latest_version = entry_value.re_versions | lnav::itertools::max();
×
223
    if (!latest_version) {
×
224
        return no_entry{};
×
225
    }
226

UNCOV
227
    auto version_url = entry_url / fmt::to_string(latest_version.value());
×
228
    curl_request version_req(version_url.string());
×
229

UNCOV
230
    curl_easy_setopt(version_req, CURLOPT_URL, version_req.get_name().c_str());
×
231
    curl_easy_setopt(version_req, CURLOPT_USERAGENT, USER_AGENT);
×
232

233
    auto version_perform_res = version_req.perform();
×
UNCOV
234
    if (version_perform_res.isErr()) {
×
235
        return lnav::console::user_message::error(
×
236
                   attr_line_t("unable to get entry version ")
×
237
                       .append_quoted(lnav::roles::symbol(version_url.string()))
×
238
                       .append(" on regex101.com"))
×
239
            .with_reason(curl_easy_strerror(version_perform_res.unwrapErr()));
×
240
    }
241

UNCOV
242
    auto version_response = version_perform_res.unwrap();
×
243
    auto version_parse_res
244
        = get_entry_handlers()
×
UNCOV
245
              .parser_for(intern_string::lookup(version_req.get_name()))
×
246
              .with_ignore_unused(true)
×
UNCOV
247
              .of(version_response);
×
248

UNCOV
249
    if (version_parse_res.isErr()) {
×
UNCOV
250
        auto parse_errors = version_parse_res.unwrapErr();
×
UNCOV
251
        return lnav::console::user_message::error(
×
UNCOV
252
                   attr_line_t("unable to get entry version ")
×
UNCOV
253
                       .append_quoted(lnav::roles::symbol(version_url.string()))
×
UNCOV
254
                       .append(" on regex101.com"))
×
UNCOV
255
            .with_reason(parse_errors[0].to_attr_line({}));
×
256
    }
257

UNCOV
258
    auto retval = version_parse_res.unwrap();
×
259

UNCOV
260
    retval.e_permalink_fragment = permalink;
×
261

UNCOV
262
    return retval;
×
263
}
264

265
struct delete_entity {
266
    std::string de_delete_code;
267
};
268

269
static const typed_json_path_container<delete_entity> DELETE_ENTITY_HANDLERS = {
270
    yajlpp::property_handler("deleteCode")
271
        .for_field(&delete_entity::de_delete_code),
272
};
273

274
Result<void, lnav::console::user_message>
275
delete_entry(const std::string& delete_code)
×
276
{
277
    curl_request cr(REGEX101_BASE_URL.string());
×
UNCOV
278
    delete_entity entity{delete_code};
×
279
    auto entity_json = DELETE_ENTITY_HANDLERS.to_string(entity);
×
280

281
    curl_easy_setopt(cr, CURLOPT_URL, cr.get_name().c_str());
×
282
    curl_easy_setopt(cr, CURLOPT_CUSTOMREQUEST, "DELETE");
×
UNCOV
283
    curl_easy_setopt(cr, CURLOPT_USERAGENT, USER_AGENT);
×
284
    curl_easy_setopt(cr, CURLOPT_POSTFIELDS, entity_json.c_str());
×
UNCOV
285
    curl_easy_setopt(cr, CURLOPT_POSTFIELDSIZE, entity_json.size());
×
286

287
    auto_mem<curl_slist> list(curl_slist_free_all);
×
288

289
    list = curl_slist_append(list, "Content-Type: application/json");
×
290

UNCOV
291
    curl_easy_setopt(cr, CURLOPT_HTTPHEADER, list.in());
×
292

293
    auto perform_res = cr.perform();
×
294
    if (perform_res.isErr()) {
×
295
        return Err(
×
296
            lnav::console::user_message::error(
×
297
                "unable to delete entry on regex101.com")
UNCOV
298
                .with_reason(curl_easy_strerror(perform_res.unwrapErr())));
×
299
    }
300

UNCOV
301
    auto response = perform_res.unwrap();
×
UNCOV
302
    auto resp_code = cr.get_response_code();
×
UNCOV
303
    if (resp_code != 200) {
×
304
        return Err(lnav::console::user_message::error(
×
305
                       "unable to delete entry on regex101.com")
306
                       .with_reason(attr_line_t()
×
307
                                        .append("received response code ")
×
308
                                        .append(lnav::roles::number(
×
UNCOV
309
                                            fmt::to_string(resp_code)))
×
UNCOV
310
                                        .append(" content ")
×
UNCOV
311
                                        .append_quoted(response)));
×
312
    }
313

314
    return Ok();
×
315
}
316

317
std::string
UNCOV
318
to_edit_url(const std::string& permalink)
×
319
{
320
    return fmt::format(FMT_STRING("https://regex101.com/r/{}"), permalink);
×
321
}
322

323
bool
UNCOV
324
unit_test::operator==(const unit_test& rhs) const
×
325
{
326
    return ut_description == rhs.ut_description
×
UNCOV
327
        && ut_test_string == rhs.ut_test_string && ut_target == rhs.ut_target
×
328
        && ut_criteria == rhs.ut_criteria;
×
329
}
330

331
bool
UNCOV
332
unit_test::operator!=(const unit_test& rhs) const
×
333
{
UNCOV
334
    return !(rhs == *this);
×
335
}
336

337
bool
UNCOV
338
entry::operator==(const entry& rhs) const
×
339
{
UNCOV
340
    return e_regex == rhs.e_regex && e_test_string == rhs.e_test_string
×
UNCOV
341
        && e_flavor == rhs.e_flavor && e_unit_tests == rhs.e_unit_tests;
×
342
}
343

344
bool
UNCOV
345
entry::operator!=(const entry& rhs) const
×
346
{
UNCOV
347
    return !(rhs == *this);
×
348
}
349

350
}  // namespace regex101::client
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