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

neomutt / neomutt / 22560814615

02 Mar 2026 03:07AM UTC coverage: 42.368% (-0.1%) from 42.484%
22560814615

push

github

flatcap
sort MenuDefs

0 of 7 new or added lines in 1 file covered. (0.0%)

2173 existing lines in 26 files now uncovered.

12014 of 28356 relevant lines covered (42.37%)

2760.43 hits per line

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

92.93
/cli/parse.c
1
/**
2
 * @file
3
 * Parse the Command Line
4
 *
5
 * @authors
6
 * Copyright (C) 2025 Richard Russon <rich@flatcap.org>
7
 *
8
 * @copyright
9
 * This program is free software: you can redistribute it and/or modify it under
10
 * the terms of the GNU General Public License as published by the Free Software
11
 * Foundation, either version 2 of the License, or (at your option) any later
12
 * version.
13
 *
14
 * This program is distributed in the hope that it will be useful, but WITHOUT
15
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17
 * details.
18
 *
19
 * You should have received a copy of the GNU General Public License along with
20
 * this program.  If not, see <http://www.gnu.org/licenses/>.
21
 */
22

23
/**
24
 * @page cli_parse Parse the Command Line
25
 *
26
 * Parse the Command Line
27
 */
28

29
#include "config.h"
30
#include <getopt.h>
31
#include <stdbool.h>
32
#include <string.h>
33
#include <sys/param.h> // IWYU pragma: keep
34
#include <unistd.h>
35
#include "mutt/lib.h"
36
#include "objects.h"
37

38
/**
39
 * LongOptions - Long option definitions for getopt_long()
40
 *
41
 * All short options have corresponding long options for clarity.
42
 * The syntax is backwards compatible - all original short options work.
43
 */
44
static const struct option LongOptions[] = {
45
  // clang-format off
46
  // Shared options
47
  { "command",             required_argument, NULL, 'e' },
48
  { "config",              required_argument, NULL, 'F' },
49
  { "debug-file",          required_argument, NULL, 'l' },
50
  { "debug-level",         required_argument, NULL, 'd' },
51
  { "mbox-type",           required_argument, NULL, 'm' },
52
  { "no-system-config",    no_argument,       NULL, 'n' },
53

54
  // Help options
55
  { "help",                no_argument,       NULL, 'h' },
56
  { "license",             no_argument,       NULL, 'L' },
57
  { "version",             no_argument,       NULL, 'v' },
58

59
  // Info options
60
  { "alias",               required_argument, NULL, 'A' },
61
  { "dump-changed-config", no_argument,       NULL, 'X' },
62
  { "dump-config",         no_argument,       NULL, 'D' },
63
  { "hide-sensitive",      no_argument,       NULL, 'S' },
64
  { "query",               required_argument, NULL, 'Q' },
65
  { "with-docs",           no_argument,       NULL, 'O' },
66

67
  // Send options
68
  { "attach",              required_argument, NULL, 'a' },
69
  { "bcc",                 required_argument, NULL, 'b' },
70
  { "cc",                  required_argument, NULL, 'c' },
71
  { "crypto",              no_argument,       NULL, 'C' },
72
  { "draft",               required_argument, NULL, 'H' },
73
  { "edit-message",        no_argument,       NULL, 'E' },
74
  { "include",             required_argument, NULL, 'i' },
75
  { "subject",             required_argument, NULL, 's' },
76

77
  // TUI options
78
  { "browser",             no_argument,       NULL, 'y' },
79
  { "check-any-mail",      no_argument,       NULL, 'z' },
80
  { "check-new-mail",      no_argument,       NULL, 'Z' },
81
  { "folder",              required_argument, NULL, 'f' },
82
  { "nntp-browser",        no_argument,       NULL, 'G' },
83
  { "nntp-server",         required_argument, NULL, 'g' },
84
  { "postponed",           no_argument,       NULL, 'p' },
85
  { "read-only",           no_argument,       NULL, 'R' },
86

87
  { NULL, 0, NULL, 0 },
88
  // clang-format on
89
};
90

91
/**
92
 * check_help_mode - Check for help mode
93
 * @param mode String to check
94
 * @retval enum #HelpMode, e.g. HM_CONFIG
95
 */
96
int check_help_mode(const char *mode)
10✔
97
{
98
  if (mutt_istr_equal(mode, "shared"))
10✔
99
    return HM_SHARED;
100
  if (mutt_istr_equal(mode, "help"))
9✔
101
    return HM_HELP;
102
  if (mutt_istr_equal(mode, "info"))
8✔
103
    return HM_INFO;
104
  if (mutt_istr_equal(mode, "send"))
7✔
105
    return HM_SEND;
106
  if (mutt_istr_equal(mode, "tui"))
6✔
107
    return HM_TUI;
108
  if (mutt_istr_equal(mode, "all"))
5✔
109
    return HM_ALL;
2✔
110
  return 0;
111
}
112

113
/**
114
 * mop_up - Eat multiple arguments
115
 * @param[in]  argc  Size of argument array
116
 * @param[in]  argv  Array of argument strings
117
 * @param[in]  index Where to start eating
118
 * @param[out] sa    Array for the results
119
 * @retval num Number of arguments eaten
120
 *
121
 * Some options, like `-A`, can take multiple arguments.
122
 * e.g. `-A apple` or `-A apple banana cherry`
123
 *
124
 * Copy the arguments into an array.
125
 */
126
static int mop_up(int argc, char *const *argv, int index, struct StringArray *sa)
146✔
127
{
128
  int count = 0;
129
  for (int i = index; i < argc; i++, count++)
161✔
130
  {
131
    // Stop if we find '--' or '-X'
132
    if ((argv[i][0] == '-') && (argv[i][1] != '\0'))
144✔
133
      break;
134

135
    ARRAY_ADD(sa, mutt_str_dup(argv[i]));
15✔
136
  }
137

138
  return count;
146✔
139
}
140

141
/**
142
 * cli_parse - Parse the Command Line
143
 * @param[in]  argc Number of arguments
144
 * @param[in]  argv Arguments
145
 * @param[out] cli  Results
146
 * @retval true Success
147
 */
148
bool cli_parse(int argc, char *const *argv, struct CommandLine *cli)
132✔
149
{
150
  if ((argc < 1) || !argv || !cli)
132✔
151
    return false;
152

153
  // Check for unsupported '=' syntax in options
154
  for (int i = 1; i < argc; i++)
387✔
155
  {
156
    if ((argv[i][0] == '-') && (argv[i][1] != '\0'))
258✔
157
    {
158
      const char *equals = strchr(argv[i], '=');
161✔
159
      if (equals)
161✔
160
      {
161
        // L10N: Neomutt doesn't support `-X=VALUE` or `--OPTION=VALUE`
162
        //       Use `-X VALUE` or `--OPTION VALUE` instead
UNCOV
163
        mutt_warning(_("Invalid option syntax: %s (use space instead of '=')"), argv[i]);
×
164
        cli->help.help = true;
×
165
        cli->help.is_set = true;
×
166
        return false;
×
167
      }
168
    }
169
  }
170

171
  // Any leading non-options must be addresses
172
  int count = mop_up(argc, argv, 1, &cli->send.addresses);
129✔
173
  if (count > 0)
129✔
174
  {
175
    cli->send.is_set = true;
7✔
176
    argc -= count;
7✔
177
    argv += count;
7✔
178
  }
179

180
  bool rc = true;
181

182
  opterr = 0; // We'll handle the errors
129✔
183
// Always initialise getopt() or the tests will fail
184
#if defined(BSD) || defined(__APPLE__)
185
  optreset = 1;
186
  optind = 1;
187
#else
188
  optind = 0;
129✔
189
#endif
190

191
  while (rc && (argc > 1) && (optind < argc))
298✔
192
  {
193
    int opt = getopt_long(argc, argv, "+:A:a:b:Cc:Dd:Ee:F:f:Gg:H:hi:l:m:nOpQ:RSs:vyZz",
169✔
194
                          LongOptions, NULL);
195
    switch (opt)
169✔
196
    {
197
      // ------------------------------------------------------------
198
      // Shared
199
      case 'F': // user config file
11✔
200
      {
201
        ARRAY_ADD(&cli->shared.user_files, mutt_str_dup(optarg));
11✔
202
        cli->shared.is_set = true;
11✔
203
        break;
11✔
204
      }
205
      case 'n': // no system config file
6✔
206
      {
207
        cli->shared.disable_system = true;
6✔
208
        cli->shared.is_set = true;
6✔
209
        break;
6✔
210
      }
211
      case 'e': // enter commands
4✔
212
      {
213
        ARRAY_ADD(&cli->shared.commands, mutt_str_dup(optarg));
4✔
214
        cli->shared.is_set = true;
4✔
215
        break;
4✔
216
      }
217
      case 'm': // mbox type
4✔
218
      {
219
        buf_strcpy(&cli->shared.mbox_type, optarg);
4✔
220
        cli->shared.is_set = true;
4✔
221
        break;
4✔
222
      }
223
      case 'd': // log level
5✔
224
      {
225
        buf_strcpy(&cli->shared.log_level, optarg);
5✔
226
        cli->shared.is_set = true;
5✔
227
        break;
5✔
228
      }
229
      case 'l': // log file
5✔
230
      {
231
        buf_strcpy(&cli->shared.log_file, optarg);
5✔
232
        cli->shared.is_set = true;
5✔
233
        break;
5✔
234
      }
235

236
      // ------------------------------------------------------------
237
      // Help
238
      case 'h': // help
13✔
239
      {
240
        if (optind < argc)
13✔
241
        {
242
          cli->help.mode = check_help_mode(argv[optind]);
10✔
243
          if (cli->help.mode != HM_NONE)
10✔
244
            optind++;
7✔
245
        }
246
        cli->help.help = true;
13✔
247
        cli->help.is_set = true;
13✔
248
        break;
13✔
249
      }
UNCOV
250
      case 'L': // license
×
251
      {
UNCOV
252
        cli->help.license = true;
×
253
        cli->help.is_set = true;
×
254
        break;
×
255
      }
256
      case 'v': // version, license
11✔
257
      {
258
        if (cli->help.version)
11✔
259
          cli->help.license = true;
3✔
260
        else
261
          cli->help.version = true;
8✔
262

263
        cli->help.is_set = true;
11✔
264
        break;
11✔
265
      }
266

267
      // ------------------------------------------------------------
268
      // Info
269
      case 'A': // alias lookup
6✔
270
      {
271
        ARRAY_ADD(&cli->info.alias_queries, mutt_str_dup(optarg));
6✔
272
        // '-A' can take multiple arguments
273
        optind += mop_up(argc, argv, optind, &cli->info.alias_queries);
6✔
274
        cli->info.is_set = true;
6✔
275
        break;
6✔
276
      }
277
      case 'D': // dump config, dump changed
8✔
278
      {
279
        if (cli->info.dump_config)
8✔
280
          cli->info.dump_changed = true;
2✔
281
        else
282
          cli->info.dump_config = true;
6✔
283

284
        cli->info.is_set = true;
8✔
285
        break;
8✔
286
      }
287
      case 'O': // one-liner help
3✔
288
      {
289
        cli->info.show_help = true;
3✔
290
        cli->info.is_set = true;
3✔
291
        break;
3✔
292
      }
293
      case 'Q': // query config&cli->send.attach
6✔
294
      {
295
        ARRAY_ADD(&cli->info.queries, mutt_str_dup(optarg));
6✔
296
        // '-Q' can take multiple arguments
297
        optind += mop_up(argc, argv, optind, &cli->info.queries);
6✔
298
        cli->info.is_set = true;
6✔
299
        break;
6✔
300
      }
301
      case 'S': // hide sensitive
3✔
302
      {
303
        cli->info.hide_sensitive = true;
3✔
304
        cli->info.is_set = true;
3✔
305
        break;
3✔
306
      }
UNCOV
307
      case 'X': // dump changed
×
308
      {
UNCOV
309
        cli->info.dump_config = true;
×
310
        cli->info.dump_changed = true;
×
311
        cli->info.is_set = true;
×
312
        break;
×
313
      }
314

315
      // ------------------------------------------------------------
316
      // Send
317
      case 'a': // attach file
5✔
318
      {
319
        ARRAY_ADD(&cli->send.attach, mutt_str_dup(optarg));
5✔
320
        // `-a` can take multiple arguments
321
        optind += mop_up(argc, argv, optind, &cli->send.attach);
5✔
322
        cli->send.is_set = true;
5✔
323
        break;
5✔
324
      }
325
      case 'b': // bcc:
4✔
326
      {
327
        ARRAY_ADD(&cli->send.bcc_list, mutt_str_dup(optarg));
4✔
328
        cli->send.is_set = true;
4✔
329
        break;
4✔
330
      }
331
      case 'C': // crypto
3✔
332
      {
333
        cli->send.use_crypto = true;
3✔
334
        cli->send.is_set = true;
3✔
335
        break;
3✔
336
      }
337
      case 'c': // cc:
4✔
338
      {
339
        ARRAY_ADD(&cli->send.cc_list, mutt_str_dup(optarg));
4✔
340
        cli->send.is_set = true;
4✔
341
        break;
4✔
342
      }
343
      case 'E': // edit file
3✔
344
      {
345
        cli->send.edit_infile = true;
3✔
346
        cli->send.is_set = true;
3✔
347
        break;
3✔
348
      }
349
      case 'H': // draft file
4✔
350
      {
351
        buf_strcpy(&cli->send.draft_file, optarg);
4✔
352
        cli->send.is_set = true;
4✔
353
        break;
4✔
354
      }
355
      case 'i': // include file
4✔
356
      {
357
        buf_strcpy(&cli->send.include_file, optarg);
4✔
358
        cli->send.is_set = true;
4✔
359
        break;
4✔
360
      }
361
      case 's': // subject:
4✔
362
      {
363
        buf_strcpy(&cli->send.subject, optarg);
4✔
364
        cli->send.is_set = true;
4✔
365
        break;
4✔
366
      }
367

368
      // ------------------------------------------------------------
369
      // TUI
370
      case 'f': // start folder
4✔
371
      {
372
        buf_strcpy(&cli->tui.folder, optarg);
4✔
373
        cli->tui.is_set = true;
4✔
374
        break;
4✔
375
      }
376
      case 'G': // list newsgroups
5✔
377
      {
378
        cli->tui.start_nntp = true;
5✔
379
        cli->tui.is_set = true;
5✔
380
        break;
5✔
381
      }
382
      case 'g': // news server
4✔
383
      {
384
        cli->tui.start_nntp = true;
4✔
385
        buf_strcpy(&cli->tui.nntp_server, optarg);
4✔
386
        cli->tui.is_set = true;
4✔
387
        break;
4✔
388
      }
389
      case 'p': // postponed
4✔
390
      {
391
        cli->tui.start_postponed = true;
4✔
392
        cli->tui.is_set = true;
4✔
393
        break;
4✔
394
      }
395
      case 'R': // read-only
4✔
396
      {
397
        cli->tui.read_only = true;
4✔
398
        cli->tui.is_set = true;
4✔
399
        break;
4✔
400
      }
401
      case 'y': // browser
4✔
402
      {
403
        cli->tui.start_browser = true;
4✔
404
        cli->tui.is_set = true;
4✔
405
        break;
4✔
406
      }
407
      case 'Z': // new mail
4✔
408
      {
409
        cli->tui.start_new_mail = true;
4✔
410
        cli->tui.is_set = true;
4✔
411
        break;
4✔
412
      }
413
      case 'z': // any mail
3✔
414
      {
415
        cli->tui.start_any_mail = true;
3✔
416
        cli->tui.is_set = true;
3✔
417
        break;
3✔
418
      }
419

420
      // ------------------------------------------------------------
421
      case -1: // end of options
5✔
422
      {
423
        for (int i = optind; i < argc; i++)
10✔
424
        {
425
          ARRAY_ADD(&cli->send.addresses, mutt_str_dup(argv[i]));
5✔
426
        }
427
        cli->send.is_set = true;
5✔
428
        optind = argc; // finish parsing
5✔
429
        break;
5✔
430
      }
431
      default: // error
16✔
432
      {
433
        if (opt == '?')
16✔
434
        {
435
          // L10N: e.g. `neomutt -X`
436
          mutt_warning(_("Invalid option: %c"), optopt);
1✔
437
        }
438
        else if (opt == ':')
15✔
439
        {
440
          // L10N: e.g. `neomutt -F`
441
          mutt_warning(_("Option %c requires an argument"), optopt);
15✔
442
        }
443

444
        cli->help.help = true;
16✔
445
        cli->help.is_set = true;
16✔
446
        rc = false; // stop parsing
447
        break;
16✔
448
      }
449
    }
450
  }
451

452
  return rc;
453
}
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