aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/getopt/small/last_getopt_opt.h
blob: 5210b6e75655e2669cd3ae314ee19eb4529e4bf2 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
#pragma once

#include "completer.h"
#include "last_getopt_handlers.h"

#include <util/string/split.h>
#include <util/generic/ptr.h>
#include <util/generic/string.h>
#include <util/generic/maybe.h>
#include <util/generic/vector.h>
#include <util/string/cast.h>

#include <stdarg.h>

namespace NLastGetopt {
    enum EHasArg {
        NO_ARGUMENT,
        REQUIRED_ARGUMENT,
        OPTIONAL_ARGUMENT,
        DEFAULT_HAS_ARG = REQUIRED_ARGUMENT
    };

    /**
     * NLastGetopt::TOpt is a storage of data about exactly one program option.
     * The data is: parse politics and help information.
     *
     * The help information consists of following:
     *   hidden or visible in help information
     *   help string
     *   argument name
     *
     * Parse politics is determined by following parameters:
     *   argument parse politics: no/optional/required/
     *   option existence: required or optional
     *   handlers. See detailed documentation: <TODO:link>
     *   default value: if the option has argument, but the option is ommited,
     *                     then the <default value> is used as the value of the argument
     *   optional value: if the option has optional-argument, the option is present in parsed string,
     *                      but the argument is omitted, then <optional value is used>
     *      in case of "not given <optional value>, omited optional argument" the <default value> is used
     *   user value: allows to store arbitary pointer for handlers
     */
    class TOpt {
    public:
        typedef TVector<char> TShortNames;
        typedef TVector<TString> TLongNames;

    protected:
        TShortNames Chars_;
        TLongNames LongNames_;

    private:
        typedef TMaybe<TString> TdOptVal;
        typedef TVector<TSimpleSharedPtr<IOptHandler>> TOptHandlers;

    public:
        bool Hidden_ = false;       // is visible in help
        TString ArgTitle_;          // the name of argument in help output
        TString Help_;              // the help string
        TString CompletionHelp_;    // the help string that's used in completion script, a shorter version of Help_
        TString CompletionArgHelp_; // the description of argument in completion script

        EHasArg HasArg_ = DEFAULT_HAS_ARG; // the argument parsing politics
        bool Required_ = false;            // option existence politics
        bool EqParseOnly_ = false;             // allows option not to read argument

        bool AllowMultipleCompletion_ = false; // let the completer know that this option can occur more than once

        bool DisableCompletionForOptions_ = false;
        bool DisableCompletionForFreeArgs_ = false;
        TShortNames DisableCompletionForChar_;
        TLongNames DisableCompletionForLongName_;
        TVector<size_t> DisableCompletionForFreeArg_;
        NComp::ICompleterPtr Completer_;

    private:
        //Handlers information
        const void* UserValue_ = nullptr;
        TdOptVal OptionalValue_;
        TdOptVal DefaultValue_;
        TOptHandlers Handlers_;

    public:
        /**
         *  Checks if given char can be a short name
         *  @param c               char to check
         */
        static bool IsAllowedShortName(unsigned char c);

        /**
         *  Checks if given string can be a long name
         *  @param name            string to check
         *  @param c               if given, the first bad charecter will be saved in c
         */
        static bool IsAllowedLongName(const TString& name, unsigned char* c = nullptr);

        /**
         *  @return one of the expected representations of the option.
         *  If the option has short names, will return "-<char>"
         *  Otherwise will return "--<long name>"
         */
        TString ToShortString() const;

        /**
         *  check if given string is one of the long names
         *
         *  @param name               string to check
         */
        bool NameIs(const TString& name) const;

        /**
         *  check if given char is one of the short names
         *
         *  @param c               char to check
         */
        bool CharIs(char c) const;

        /**
         *  If string has long names - will return one of them
         *  Otherwise will throw
         */
        TString GetName() const;

        /**
         *  adds short alias for the option
         *
         *  @param c               new short name
         *
         *  @return self
         */
        TOpt& AddShortName(unsigned char c);

        /**
         *  return all short names of the option
         */
        const TShortNames& GetShortNames() const {
            return Chars_;
        }

        /**
         *  adds long alias for the option
         *
         *  @param name              new long name
         *
         *  @return self
         */
        TOpt& AddLongName(const TString& name);

        /**
         *  return all long names of the option
         */
        const TLongNames& GetLongNames() const {
            return LongNames_;
        }

        /**
         *  @return one of short names of the opt. If there is no short names exception is raised.
         */
        char GetChar() const;

        /**
         *  @return one of short names of the opt. If there is no short names '\0' returned.
         */
        char GetCharOr0() const;

        /**
         *  @returns argument parsing politics
         */
        const EHasArg& GetHasArg() const {
            return HasArg_;
        }

        /**
         *  sets argument parsing politics
         *
         *  Note: its better use one of RequiredArgument/NoArgument/OptionalArgument methods
         *
         *  @param hasArg      new argument parsing mode
         *  @return self
         */
        TOpt& HasArg(EHasArg hasArg) {
            HasArg_ = hasArg;
            return *this;
        }

        /**
         *  @returns argument title
         */
        TString GetArgTitle() const {
            return ArgTitle_;
        }

        /**
         *  sets argument parsing politics into REQUIRED_ARGUMENT
         *
         *  @param title      the new name of argument in help output
         *  @return self
         */
        TOpt& RequiredArgument(const TString& title = "") {
            ArgTitle_ = title;
            return HasArg(REQUIRED_ARGUMENT);
        }

        /**
         *  sets argument parsing politics into NO_ARGUMENT
         *
         *  @return self
         */
        TOpt& NoArgument() {
            return HasArg(NO_ARGUMENT);
        }

        /**
         *  sets argument parsing politics into OPTIONAL_ARGUMENT
         *  for details see NLastGetopt::TOpt
         *
         *  @param title      the new name of argument in help output
         *  @return self
         */
        TOpt& OptionalArgument(const TString& title = "") {
            ArgTitle_ = title;
            return HasArg(OPTIONAL_ARGUMENT);
        }

        /**
         *  sets argument parsing politics into OPTIONAL_ARGUMENT
         *  sets the <optional value> into given
         *
         *  for details see NLastGetopt::TOpt
         *
         *  @param val        the new <optional value>
         *  @param title      the new name of argument in help output
         *  @return self
         */
        TOpt& OptionalValue(const TString& val, const TString& title = "") {
            OptionalValue_ = val;
            return OptionalArgument(title);
        }

        /**
         *  checks if "argument parsing politics" is OPTIONAL_ARGUMENT and the <optional value> is set.
         */
        bool HasOptionalValue() const {
            return OPTIONAL_ARGUMENT == HasArg_ && OptionalValue_;
        }

        /**
         *  @return optional value
         *  throws exception if optional value wasn't set
         */
        const TString& GetOptionalValue() const {
            return *OptionalValue_;
        }

        /**
         *  sets <default value>
         *  @return self
         */
        template <typename T>
        TOpt& DefaultValue(const T& val) {
            DefaultValue_ = ToString(val);
            return *this;
        }

        /**
         *  checks if default value is set.
         */
        bool HasDefaultValue() const {
            return DefaultValue_.Defined();
        }

        /**
         *  @return default value
         *  throws exception if <default value> wasn't set
         */
        const TString& GetDefaultValue() const {
            return *DefaultValue_;
        }

        /**
         *  sets the option to be required
         *  @return self
         */
        TOpt& Required() {
            Required_ = true;
            return *this;
        }

        /**
         *  allow only --option=arg parsing and disable --option arg
         *  @return self
         */
        TOpt& DisableSpaceParse() {
            Y_ASSERT(GetHasArg() == OPTIONAL_ARGUMENT);
            EqParseOnly_ = true;
            return *this;
        }

        /**
         *  @return true if only --option=arg parse allowed
         */
        bool IsEqParseOnly() const {
            return EqParseOnly_;
        }

        /**
         *  sets the option to be optional
         *  @return self
         */
        TOpt& Optional() {
            Required_ = false;
            return *this;
        }

        /**
         *  @return true if the option is required
         */
        bool IsRequired() const {
            return Required_;
        }

        /**
         *  sets the option to be hidden (invisible in help)
         *  @return self
         */
        TOpt& Hidden() {
            Hidden_ = true;
            return *this;
        }

        /**
         *  @return true if the option is hidden
         */
        bool IsHidden() const {
            return Hidden_;
        }

        /**
         *  sets the <user value>
         *  @return self
         *  for details see NLastGetopt::TOpt
         */
        TOpt& UserValue(const void* userval) {
            UserValue_ = userval;
            return *this;
        }

        /**
         *  @return user value
         */
        const void* UserValue() const {
            return UserValue_;
        }

        /**
         * Set help string that appears with `--help`. Unless `CompletionHelp` is given, this message will also be used
         * in completion script. In this case, don't make it too long, don't start it with a capital letter and don't
         * end it with a full stop.
         *
         * Note that `Help`, `CompletionHelp` and `CompletionArgHelp` are not the same. `Help` is printed in program
         * usage (when you call `program --help`), `CompletionHelp` is printed when completer lists available
         * options, and `CompletionArgHelp` is printed when completer shows available values for the option.
         *
         * Example of good help message:
         *
         * ```
         * opts.AddLongOption('t', "timeout")
         *     .Help("specify query timeout in milliseconds")
         *     .CompletionHelp("specify query timeout")
         *     .CompletionArgHelp("query timeout (ms) [default=500]");
         * ```
         *
         * Notice how `Help` and `CompletionArgHelp` have units in them, but `CompletionHelp` don't.
         *
         * Another good example is the help option:
         *
         * ```
         * opts.AddLongOption('h', "help")
         *     .Help("print this message and exit")
         *     .CompletionHelp("print help message and exit");
         * ```
         *
         * Notice how `Help` mentions 'this message', but `CompletionHelp` mentions just 'help message'.
         *
         * See more on completion descriptions codestyle:
         * https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide#L43
         */
        TOpt& Help(const TString& help) {
            Help_ = help;
            return *this;
        }

        /**
         * Get help string.
         */
        const TString& GetHelp() const {
            return Help_;
        }

        /**
         * Set help string that appears when argument completer lists available options.
         *
         * See `Help` function for info on how this is different from setting `Help` and `CompletionArgHelp`.
         *
         * Use shorter messages for this message. Don't start them with a capital letter and don't end them
         * with a full stop. De aware that argument name and default value will not be printed by completer.
         *
         * In zsh, these messages will look like this:
         *
         * ```
         * $ program -<tab><tab>
         *  -- option --
         * --help    -h  -- print help message and exit
         * --timeout -t  -- specify query timeout
         * ```
         */
        TOpt& CompletionHelp(const TString& help) {
            CompletionHelp_ = help;
            return *this;
        }

        /**
         * Get help string that appears when argument completer lists available options.
         */
        const TString& GetCompletionHelp() const {
            return CompletionHelp_ ? CompletionHelp_ : Help_;
        }

        /**
         * Set help string that appears when completer suggests available values.
         *
         * See `Help` function for info on how this is different from setting `Help` and `CompletionHelp`.
         *
         * In zsh, these messages will look like this:
         *
         * ```
         * $ program --timeout <tab><tab>
         *  -- query timeout (ms) [default=500] --
         * 50     100     250     500     1000
         * ```
         */
        TOpt& CompletionArgHelp(const TString& help) {
            CompletionArgHelp_ = help;
            return *this;
        }

        /**
         *  @return argument help string for use in completion script.
         */
        const TString& GetCompletionArgHelp() const {
            return CompletionArgHelp_ ? CompletionArgHelp_ : ArgTitle_;
        }

        /**
         * Let the completer know that this option can occur more than once.
         */
        TOpt& AllowMultipleCompletion(bool allowMultipleCompletion = true) {
            AllowMultipleCompletion_ = allowMultipleCompletion;
            return *this;
        }

        /**
         * @return true if completer will offer completion for this option multiple times.
         */
        bool MultipleCompletionAllowed() const {
            return AllowMultipleCompletion_;
        }

        /**
         * Tell the completer to disable further completion if this option is present.
         * This is useful for options like `--help`.
         *
         * Note: this only works in zsh.
         *
         * @return self
         */
        TOpt& IfPresentDisableCompletion(bool value = true) {
            IfPresentDisableCompletionForOptions(value);
            IfPresentDisableCompletionForFreeArgs(value);
            return *this;
        }

        /**
         * Tell the completer to disable completion for all options if this option is already present in the input.
         * Free arguments will still be completed.
         *
         * Note: this only works in zsh.
         *
         * @return self
         */
        TOpt& IfPresentDisableCompletionForOptions(bool value = true) {
            DisableCompletionForOptions_ = value;
            return *this;
        }

        /**
         * Tell the completer to disable option `c` if this option is already present in the input.
         * For example, if you have two options `-a` and `-r` that are mutually exclusive, disable `-r` for `-a` and
         * disable `-a` for `-r`, like this:
         *
         * ```
         * opts.AddLongOption('a', "acquire").IfPresentDisableCompletionFor('r');
         * opts.AddLongOption('r', "release").IfPresentDisableCompletionFor('a');
         * ```
         *
         * This way, if user enabled option `-a`, completer will not suggest option `-r`.
         *
         * Note that we don't have to disable all flags for a single option. That is, disabling `-r` in the above
         * example disables `--release` automatically.
         *
         * Note: this only works in zsh.
         *
         * @param c char option that should be disabled when completer hits this option.
         */
        TOpt& IfPresentDisableCompletionFor(char c) {
            DisableCompletionForChar_.push_back(c);
            return *this;
        }

        /**
         * Like `IfPresentDisableCompletionFor(char c)`, but for long options.
         */
        TOpt& IfPresentDisableCompletionFor(const TString& name) {
            DisableCompletionForLongName_.push_back(name);
            return *this;
        }

        /**
         * Like `IfPresentDisableCompletionFor(char c)`, but for long options.
         */
        TOpt& IfPresentDisableCompletionFor(const TOpt& opt);

        /**
         * Tell the completer to disable completion for the given free argument if this option is present.
         *
         * Note: this only works in zsh.
         *
         * @param arg index of free arg
         */
        TOpt& IfPresentDisableCompletionForFreeArg(size_t index) {
            DisableCompletionForFreeArg_.push_back(index);
            return *this;
        }

        /**
         * Assign a completer for this option.
         */
        TOpt& Completer(NComp::ICompleterPtr completer) {
            Completer_ = std::move(completer);
            return *this;
        }

        /**
         * Tell the completer to disable completion for the all free arguments if this option is present.
         *
         * Note: this only works in zsh.
         */
        TOpt& IfPresentDisableCompletionForFreeArgs(bool value = true) {
            DisableCompletionForFreeArgs_ = value;
            return *this;
        }

        /**
         * Run handlers for this option.
         */
        void FireHandlers(const TOptsParser* parser) const;

    private:
        TOpt& HandlerImpl(IOptHandler* handler) {
            Handlers_.push_back(handler);
            return *this;
        }

    public:
        template <typename TpFunc>
        TOpt& Handler0(TpFunc func) { // functor taking no parameters
            return HandlerImpl(new NPrivate::THandlerFunctor0<TpFunc>(func));
        }

        template <typename TpFunc>
        TOpt& Handler1(TpFunc func) { // functor taking one parameter
            return HandlerImpl(new NPrivate::THandlerFunctor1<TpFunc>(func));
        }
        template <typename TpArg, typename TpFunc>
        TOpt& Handler1T(TpFunc func) {
            return HandlerImpl(new NPrivate::THandlerFunctor1<TpFunc, TpArg>(func));
        }
        template <typename TpArg, typename TpFunc>
        TOpt& Handler1T(const TpArg& def, TpFunc func) {
            return HandlerImpl(new NPrivate::THandlerFunctor1<TpFunc, TpArg>(func, def));
        }
        template <typename TpArg, typename TpArg2, typename TpFunc>
        TOpt& Handler1T2(const TpArg2& def, TpFunc func) {
            return HandlerImpl(new NPrivate::THandlerFunctor1<TpFunc, TpArg>(func, def));
        }

        TOpt& Handler(void (*f)()) {
            return Handler0(f);
        }
        TOpt& Handler(void (*f)(const TOptsParser*)) {
            return Handler1(f);
        }

        TOpt& Handler(TAutoPtr<IOptHandler> handler) {
            return HandlerImpl(handler.Release());
        }

        template <typename T> // T extends IOptHandler
        TOpt& Handler(TAutoPtr<T> handler) {
            return HandlerImpl(handler.Release());
        }

        // Stores FromString<T>(arg) in *target
        // T maybe anything with FromString<T>(const TStringBuf&) defined
        template <typename TpVal, typename T>
        TOpt& StoreResultT(T* target) {
            return Handler1T<TpVal>(NPrivate::TStoreResultFunctor<T, TpVal>(target));
        }

        template <typename T>
        TOpt& StoreResult(T* target) {
            return StoreResultT<T>(target);
        }

        // Uses TMaybe<T> to store FromString<T>(arg)
        template <typename T>
        TOpt& StoreResult(TMaybe<T>* target) {
            return StoreResultT<T>(target);
        }

        template <typename TpVal, typename T, typename TpDef>
        TOpt& StoreResultT(T* target, const TpDef& def) {
            return Handler1T<TpVal>(def, NPrivate::TStoreResultFunctor<T, TpVal>(target));
        }

        template <typename T, typename TpDef>
        TOpt& StoreResult(T* target, const TpDef& def) {
            return StoreResultT<T>(target, def);
        }

        template <typename T>
        TOpt& StoreResultDef(T* target) {
            DefaultValue_ = ToString(*target);
            return StoreResultT<T>(target, *target);
        }

        template <typename T, typename TpDef>
        TOpt& StoreResultDef(T* target, const TpDef& def) {
            DefaultValue_ = ToString(def);
            return StoreResultT<T>(target, def);
        }

        // Sugar for storing flags (option without arguments) to boolean vars
        TOpt& SetFlag(bool* target) {
            return DefaultValue("0").StoreResult(target, true);
        }

        // Similar to store_true in Python's argparse
        TOpt& StoreTrue(bool* target) {
            return NoArgument().SetFlag(target);
        }

        template <typename TpVal, typename T, typename TpFunc>
        TOpt& StoreMappedResultT(T* target, const TpFunc& func) {
            return Handler1T<TpVal>(NPrivate::TStoreMappedResultFunctor<T, TpFunc, TpVal>(target, func));
        }

        template <typename T, typename TpFunc>
        TOpt& StoreMappedResult(T* target, const TpFunc& func) {
            return StoreMappedResultT<T>(target, func);
        }

        // Stores given value in *target if the option is present.
        // TValue must be a copyable type, constructible from TParam.
        // T must be a copyable type, assignable from TValue.
        template <typename TValue, typename T, typename TParam>
        TOpt& StoreValueT(T* target, const TParam& value) {
            return Handler1(NPrivate::TStoreValueFunctor<T, TValue>(target, value));
        }

        // save value as target type
        template <typename T, typename TParam>
        TOpt& StoreValue(T* target, const TParam& value) {
            return StoreValueT<T>(target, value);
        }

        // save value as its original type (2nd template parameter)
        template <typename T, typename TValue>
        TOpt& StoreValue2(T* target, const TValue& value) {
            return StoreValueT<TValue>(target, value);
        }

        // Appends FromString<T>(arg) to *target for each argument
        template<class Container>
        TOpt& AppendTo(Container* target) {
            return Handler1T<typename Container::value_type>([target](auto&& value) { target->push_back(std::move(value)); });
        }

        // Appends FromString<T>(arg) to *target for each argument
        template <typename T>
        TOpt& InsertTo(THashSet<T>* target) {
            return Handler1T<T>([target](auto&& value) { target->insert(std::move(value)); });
        }

        // Emplaces TString arg to *target for each argument
        template <typename T>
        TOpt& EmplaceTo(TVector<T>* target) {
            return Handler1T<TString>([target](TString arg) { target->emplace_back(std::move(arg)); } );
        }

        template <class Container>
        TOpt& SplitHandler(Container* target, const char delim) {
            return Handler(new NLastGetopt::TOptSplitHandler<Container>(target, delim));
        }

        template <class Container>
        TOpt& RangeSplitHandler(Container* target, const char elementsDelim, const char rangesDelim) {
            return Handler(new NLastGetopt::TOptRangeSplitHandler<Container>(target, elementsDelim, rangesDelim));
        }

        template <class TpFunc>
        TOpt& KVHandler(TpFunc func, const char kvdelim = '=') {
            return Handler(new NLastGetopt::TOptKVHandler<TpFunc>(func, kvdelim));
        }
    };

    /**
     * NLastGetopt::TFreeArgSpec is a storage of data about free argument.
     * The data is help information and (maybe) linked named argument.
     *
     * The help information consists of following:
     *   help string
     *   argument name (title)
     */
    struct TFreeArgSpec {
        TFreeArgSpec() = default;
        TFreeArgSpec(const TString& title, const TString& help = TString(), bool optional = false)
            : Title_(title)
            , Help_(help)
            , Optional_(optional)
        {
        }

        TString Title_;
        TString Help_;
        TString CompletionArgHelp_;

        bool Optional_ = false;
        NComp::ICompleterPtr Completer_ = nullptr;

        /**
         * Check if this argument have default values for its title and help.
         */
        bool IsDefault() const {
            return Title_.empty() && Help_.empty();
        }

        /**
         * Set argument title.
         */
        TFreeArgSpec& Title(TString title) {
            Title_ = std::move(title);
            return *this;
        }

        /**
         * Get argument title. If title is empty, returns a default one.
         */
        TStringBuf GetTitle(TStringBuf defaultTitle) const {
            return Title_ ? TStringBuf(Title_) : defaultTitle;
        }

        /**
         * Set help string that appears with `--help`. Unless `CompletionHelp` is given, this message will also be used
         * in completion script. In this case, don't make it too long, don't start it with a capital letter and don't
         * end it with a full stop.
         *
         * See `TOpt::Help` function for more on how `Help` and `CompletionArgHelp` differ one from another.
         */
        TFreeArgSpec& Help(TString help) {
            Help_ = std::move(help);
            return *this;
        }

        /**
         * Get help string that appears with `--help`.
         */
        TStringBuf GetHelp() const {
            return Help_;
        }

        /**
         * Set help string that appears when completer suggests values fot this argument.
         */
        TFreeArgSpec& CompletionArgHelp(TString completionArgHelp) {
            CompletionArgHelp_ = std::move(completionArgHelp);
            return *this;
        }

        /**
         * Get help string that appears when completer suggests values fot this argument.
         */
        TStringBuf GetCompletionArgHelp(TStringBuf defaultTitle) const {
            return CompletionArgHelp_ ? TStringBuf(CompletionArgHelp_) : GetTitle(defaultTitle);
        }

        /**
         * Mark this argument as optional. This setting only affects help printing, it doesn't affect parsing.
         */
        TFreeArgSpec& Optional(bool optional = true) {
            Optional_ = optional;
            return *this;
        }

        /**
         * Check if this argument is optional.
         */
        bool IsOptional() const {
            return Optional_;
        }

        /**
         * Set completer for this argument.
         */
        TFreeArgSpec& Completer(NComp::ICompleterPtr completer) {
            Completer_ = std::move(completer);
            return *this;
        }
    };
}