Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

# coding=utf-8 

import argparse 

import re as _re 

import sys 

from argparse import OPTIONAL, ZERO_OR_MORE, ONE_OR_MORE, REMAINDER, PARSER, ArgumentError, _ 

try: 

from typing import List, Dict, Tuple, Callable, Union 

except: 

pass 

# from colorama import Fore 

 

 

class _RangeAction(object): 

def __init__(self, nargs: Union[int, str, Tuple[int, int], None]): 

self.nargs_min = None 

self.nargs_max = None 

 

# pre-process special ranged nargs 

if isinstance(nargs, tuple): 

if len(nargs) != 2 or not isinstance(nargs[0], int) or not isinstance(nargs[1], int): 

raise ValueError('Ranged values for nargs must be a tuple of 2 integers') 

if nargs[0] >= nargs[1]: 

raise ValueError('Invalid nargs range. The first value must be less than the second') 

if nargs[0] < 0: 

raise ValueError('Negative numbers are invalid for nargs range.') 

narg_range = nargs 

self.nargs_min = nargs[0] 

self.nargs_max = nargs[1] 

if narg_range[0] == 0: 

if narg_range[1] > 1: 

self.nargs_adjusted = '*' 

else: 

# this shouldn't use a range tuple, but yet here we are 

self.nargs_adjusted = '?' 

else: 

self.nargs_adjusted = '+' 

else: 

self.nargs_adjusted = nargs 

 

 

class _StoreRangeAction(argparse._StoreAction, _RangeAction): 

def __init__(self, 

option_strings, 

dest, 

nargs=None, 

const=None, 

default=None, 

type=None, 

choices=None, 

required=False, 

help=None, 

metavar=None): 

 

_RangeAction.__init__(self, nargs) 

 

argparse._StoreAction.__init__(self, 

option_strings=option_strings, 

dest=dest, 

nargs=self.nargs_adjusted, 

const=const, 

default=default, 

type=type, 

choices=choices, 

required=required, 

help=help, 

metavar=metavar) 

 

 

class _AppendRangeAction(argparse._AppendAction, _RangeAction): 

def __init__(self, 

option_strings, 

dest, 

nargs=None, 

const=None, 

default=None, 

type=None, 

choices=None, 

required=False, 

help=None, 

metavar=None): 

 

_RangeAction.__init__(self, nargs) 

 

argparse._AppendAction.__init__(self, 

option_strings=option_strings, 

dest=dest, 

nargs=self.nargs_adjusted, 

const=const, 

default=default, 

type=type, 

choices=choices, 

required=required, 

help=help, 

metavar=metavar) 

 

 

def register_custom_actions(parser: argparse.ArgumentParser): 

"""Register custom argument action types""" 

parser.register('action', None, _StoreRangeAction) 

parser.register('action', 'store', _StoreRangeAction) 

parser.register('action', 'append', _AppendRangeAction) 

 

 

class AutoCompleter(object): 

"""Automatically command line tab completion based on argparse parameters""" 

 

class _ArgumentState(object): 

def __init__(self): 

self.min = None 

self.max = None 

self.count = 0 

self.needed = False 

self.variable = False 

 

def reset(self): 

"""reset tracking values""" 

self.min = None 

self.max = None 

self.count = 0 

self.needed = False 

self.variable = False 

 

def __init__(self, 

parser: argparse.ArgumentParser, 

token_start_index: int = 1, 

arg_choices: Dict[str, Union[List, Tuple, Callable]] = None, 

subcmd_args_lookup: dict = None, 

tab_for_arg_help: bool = True): 

""" 

AutoCompleter interprets the argparse.ArgumentParser internals to automatically 

generate the completion options for each argument. 

 

How to supply completion options for each argument: 

argparse Choices 

- pass a list of values to the choices parameter of an argparse argument. 

ex: parser.add_argument('-o', '--options', dest='options', choices=['An Option', 'SomeOtherOption']) 

 

arg_choices dictionary lookup 

arg_choices is a dict() mapping from argument name to one of 3 possible values: 

ex: 

parser = argparse.ArgumentParser() 

parser.add_argument('-o', '--options', dest='options') 

choices = {} 

mycompleter = AutoCompleter(parser, completer, 1, choices) 

 

- static list - provide a static list for each argument name 

ex: 

choices['options'] = ['An Option', 'SomeOtherOption'] 

 

- choices function - provide a function that returns a list for each argument name 

ex: 

def generate_choices(): 

return ['An Option', 'SomeOtherOption'] 

choices['options'] = generate_choices 

 

- custom completer function - provide a completer function that will return the list 

of completion arguments 

ex 1: 

def my_completer(text: str, line: str, begidx: int, endidx:int): 

my_choices = [...] 

return my_choices 

choices['options'] = (my_completer) 

ex 2: 

def my_completer(text: str, line: str, begidx: int, endidx:int, extra_param: str, another: int): 

my_choices = [...] 

return my_choices 

completer_params = {'extra_param': 'my extra', 'another': 5} 

choices['options'] = (my_completer, completer_params) 

 

How to supply completion choice lists or functions for sub-commands: 

subcmd_args_lookup is used to supply a unique pair of arg_choices and subcmd_args_lookup 

for each sub-command in an argparser subparser group. 

This requires your subparser group to be named with the dest parameter 

ex: 

parser = ArgumentParser() 

subparsers = parser.add_subparsers(title='Actions', dest='action') 

 

subcmd_args_lookup maps a named subparser group to a subcommand group dictionary 

The subcommand group dictionary maps subcommand names to tuple(arg_choices, subcmd_args_lookup) 

 

 

:param parser: ArgumentParser instance 

:param token_start_index: index of the token to start parsing at 

:param arg_choices: dictionary mapping from argparse argument 'dest' name to list of choices 

:param subcmd_args_lookup: mapping a sub-command group name to a tuple to fill the child\ 

AutoCompleter's arg_choices and subcmd_args_lookup parameters 

""" 

if not subcmd_args_lookup: 

subcmd_args_lookup = {} 

forward_arg_choices = True 

else: 

forward_arg_choices = False 

self._parser = parser 

self._arg_choices = arg_choices.copy() if arg_choices is not None else {} 

self._token_start_index = token_start_index 

self._tab_for_arg_help = tab_for_arg_help 

 

self._flags = [] # all flags in this command 

self._flags_without_args = [] # all flags that don't take arguments 

self._flag_to_action = {} # maps flags to the argparse action object 

self._positional_actions = [] # argument names for positional arguments (by position index) 

self._positional_completers = {} # maps action name to sub-command autocompleter: 

# action_name -> dict(sub_command -> completer) 

 

# Start digging through the argparse structures. 

# _actions is the top level container of parameter definitions 

for action in self._parser._actions: 

# if there are choices defined, record them in the arguments dictionary 

if action.choices is not None: 

self._arg_choices[action.dest] = action.choices 

 

# if the parameter is flag based, it will have option_strings 

if action.option_strings: 

# record each option flag 

for option in action.option_strings: 

self._flags.append(option) 

self._flag_to_action[option] = action 

if action.nargs == 0: 

self._flags_without_args.append(option) 

else: 

self._positional_actions.append(action) 

 

if isinstance(action, argparse._SubParsersAction): 

sub_completers = {} 

sub_commands = [] 

args_for_action = subcmd_args_lookup[action.dest]\ 

if action.dest in subcmd_args_lookup.keys() else {} 

for subcmd in action.choices: 

(subcmd_args, subcmd_lookup) = args_for_action[subcmd] if \ 

subcmd in args_for_action.keys() else \ 

(arg_choices, subcmd_args_lookup) if forward_arg_choices else ({}, {}) 

subcmd_start = token_start_index + len(self._positional_actions) 

sub_completers[subcmd] = AutoCompleter(action.choices[subcmd], subcmd_start, 

arg_choices=subcmd_args, 

subcmd_args_lookup=subcmd_lookup) 

sub_commands.append(subcmd) 

self._positional_completers[action.dest] = sub_completers 

self._arg_choices[action.dest] = sub_commands 

 

def complete_command(self, tokens: List[str], text: str, line: str, begidx: int, endidx: int) -> List[str]: 

"""Complete the command using the argparse metadata and provided argument dictionary""" 

# Count which positional argument index we're at now. Loop through all tokens on the command line so far 

# Skip any flags or flag parameter tokens 

next_pos_arg_index = 0 

 

pos_arg = AutoCompleter._ArgumentState() 

pos_action = None 

 

flag_arg = AutoCompleter._ArgumentState() 

flag_action = None 

 

matched_flags = [] 

current_is_positional = False 

consumed_arg_values = {} # dict(arg_name -> [values, ...]) 

 

def consume_flag_argument() -> None: 

"""Consuming token as a flag argument""" 

# we're consuming flag arguments 

# if this is not empty and is not another potential flag, count towards flag arguments 

if len(token) > 0 and not token[0] in self._parser.prefix_chars and flag_action is not None: 

flag_arg.count += 1 

 

# does this complete a option item for the flag 

arg_choices = self._resolve_choices_for_arg(flag_action) 

# if the current token matches the current position's autocomplete argument list, 

# track that we've used it already. Unless this is the current token, then keep it. 

if not is_last_token and token in arg_choices: 

consumed_arg_values.setdefault(flag_action.dest, []) 

consumed_arg_values[flag_action.dest].append(token) 

 

def consume_positional_argument() -> None: 

"""Consuming token as positional argument""" 

pos_arg.count += 1 

 

# does this complete a option item for the flag 

arg_choices = self._resolve_choices_for_arg(pos_action) 

# if the current token matches the current position's autocomplete argument list, 

# track that we've used it already. Unless this is the current token, then keep it. 

if not is_last_token and token in arg_choices: 

consumed_arg_values.setdefault(pos_action.dest, []) 

consumed_arg_values[pos_action.dest].append(token) 

 

is_last_token = False 

for idx, token in enumerate(tokens): 

is_last_token = idx >= len(tokens) - 1 

# Only start at the start token index 

if idx >= self._token_start_index: 

current_is_positional = False 

# Are we consuming flag arguments? 

if not flag_arg.needed: 

# we're not consuming flag arguments, is the current argument a potential flag? 

if len(token) > 0 and token[0] in self._parser.prefix_chars and\ 

(is_last_token or (not is_last_token and token != '-')): 

# reset some tracking values 

flag_arg.reset() 

# don't reset positional tracking because flags can be interspersed anywhere between positionals 

flag_action = None 

 

# does the token fully match a known flag? 

if token in self._flag_to_action.keys(): 

flag_action = self._flag_to_action[token] 

elif self._parser.allow_abbrev: 

candidates_flags = [flag for flag in self._flag_to_action.keys() if flag.startswith(token)] 

if len(candidates_flags) == 1: 

flag_action = self._flag_to_action[candidates_flags[0]] 

 

if flag_action is not None: 

# resolve argument counts 

self._process_action_nargs(flag_action, flag_arg) 

if not is_last_token and not isinstance(flag_action, argparse._AppendAction): 

matched_flags.extend(flag_action.option_strings) 

 

# current token isn't a potential flag 

# - does the last flag accept variable arguments? 

# - have we reached the max arg count for the flag? 

elif not flag_arg.variable or flag_arg.count >= flag_arg.max: 

# previous flag doesn't accept variable arguments, count this as a positional argument 

 

# reset flag tracking variables 

flag_arg.reset() 

flag_action = None 

current_is_positional = True 

 

if len(token) > 0 and pos_action is not None and pos_arg.count < pos_arg.max: 

# we have positional action match and we haven't reached the max arg count, consume 

# the positional argument and move on. 

consume_positional_argument() 

elif pos_action is None or pos_arg.count >= pos_arg.max: 

# if we don't have a current positional action or we've reached the max count for the action 

# close out the current positional argument state and set up for the next one 

pos_index = next_pos_arg_index 

next_pos_arg_index += 1 

pos_arg.reset() 

pos_action = None 

 

# are we at a sub-command? If so, forward to the matching completer 

if pos_index < len(self._positional_actions): 

action = self._positional_actions[pos_index] 

pos_name = action.dest 

if pos_name in self._positional_completers.keys(): 

sub_completers = self._positional_completers[pos_name] 

if token in sub_completers.keys(): 

return sub_completers[token].complete_command(tokens, text, line, 

begidx, endidx) 

pos_action = action 

self._process_action_nargs(pos_action, pos_arg) 

consume_positional_argument() 

 

elif not is_last_token and pos_arg.max is not None: 

pos_action = None 

pos_arg.reset() 

 

else: 

consume_flag_argument() 

 

else: 

consume_flag_argument() 

 

# don't reset this if we're on the last token - this allows completion to occur on the current token 

if not is_last_token and flag_arg.min is not None: 

flag_arg.needed = flag_arg.count < flag_arg.min 

 

# if we don't have a flag to populate with arguments and the last token starts with 

# a flag prefix then we'll complete the list of flag options 

completion_results = [] 

if not flag_arg.needed and len(tokens[-1]) > 0 and tokens[-1][0] in self._parser.prefix_chars: 

return AutoCompleter.basic_complete(text, line, begidx, endidx, 

[flag for flag in self._flags if flag not in matched_flags]) 

# we're not at a positional argument, see if we're in a flag argument 

elif not current_is_positional: 

# current_items = [] 

if flag_action is not None: 

consumed = consumed_arg_values[flag_action.dest]\ 

if flag_action.dest in consumed_arg_values.keys() else [] 

# current_items.extend(self._resolve_choices_for_arg(flag_action, consumed)) 

completion_results = self._complete_for_arg(flag_action, text, line, begidx, endidx, consumed) 

if not completion_results: 

self._print_action_help(flag_action) 

 

# ok, we're not a flag, see if there's a positional argument to complete 

else: 

if pos_action is not None: 

pos_name = pos_action.dest 

consumed = consumed_arg_values[pos_name] if pos_name in consumed_arg_values.keys() else [] 

completion_results = self._complete_for_arg(pos_action, text, line, begidx, endidx, consumed) 

if not completion_results: 

self._print_action_help(pos_action) 

 

return completion_results 

 

@staticmethod 

def _process_action_nargs(action: argparse.Action, arg_state: _ArgumentState) -> None: 

if isinstance(action, _RangeAction): 

arg_state.min = action.nargs_min 

arg_state.max = action.nargs_max 

arg_state.variable = True 

if arg_state.min is None or arg_state.max is None: 

if action.nargs is None: 

arg_state.min = 1 

arg_state.max = 1 

elif action.nargs == '+': 

arg_state.min = 1 

arg_state.max = float('inf') 

arg_state.variable = True 

elif action.nargs == '*': 

arg_state.min = 0 

arg_state.max = float('inf') 

arg_state.variable = True 

elif action.nargs == '?': 

arg_state.min = 0 

arg_state.max = 1 

arg_state.variable = True 

else: 

arg_state.min = action.nargs 

arg_state.max = action.nargs 

 

def _complete_for_arg(self, action: argparse.Action, 

text: str, 

line: str, 

begidx: int, 

endidx: int, 

used_values=list()) -> List[str]: 

if action.dest in self._arg_choices.keys(): 

arg_choices = self._arg_choices[action.dest] 

 

if isinstance(arg_choices, tuple) and len(arg_choices) > 0 and callable(arg_choices[0]): 

completer = arg_choices[0] 

list_args = None 

kw_args = None 

for index in range(1, len(arg_choices)): 

if isinstance(arg_choices[index], list): 

list_args = arg_choices[index] 

elif isinstance(arg_choices[index], dict): 

kw_args = arg_choices[index] 

if list_args is not None and kw_args is not None: 

return completer(text, line, begidx, endidx, *list_args, **kw_args) 

elif list_args is not None: 

return completer(text, line, begidx, endidx, *list_args) 

elif kw_args is not None: 

return completer(text, line, begidx, endidx, **kw_args) 

else: 

return completer(text, line, begidx, endidx) 

else: 

return AutoCompleter.basic_complete(text, line, begidx, endidx, 

self._resolve_choices_for_arg(action, used_values)) 

 

return [] 

 

def _resolve_choices_for_arg(self, action: argparse.Action, used_values=list()) -> List[str]: 

if action.dest in self._arg_choices.keys(): 

args = self._arg_choices[action.dest] 

if callable(args): 

args = args() 

 

try: 

iter(args) 

except TypeError: 

pass 

else: 

# filter out arguments we already used 

args = [arg for arg in args if arg not in used_values] 

 

if len(args) > 0: 

return args 

 

return [] 

 

def _print_action_help(self, action: argparse.Action) -> None: 

if not self._tab_for_arg_help: 

return 

if action.option_strings: 

flags = ', '.join(action.option_strings) 

param = '' 

if action.nargs is None or action.nargs != 0: 

param += ' ' + str(action.dest).upper() 

 

prefix = '{}{}'.format(flags, param) 

else: 

prefix = '{}'.format(str(action.dest).upper()) 

 

prefix = ' {0: <{width}} '.format(prefix, width=20) 

pref_len = len(prefix) 

help_lines = action.help.splitlines() 

if len(help_lines) == 1: 

print('\nHint:\n{}{}\n'.format(prefix, help_lines[0])) 

else: 

out_str = '\n{}'.format(prefix) 

out_str += '\n{0: <{width}}'.format('', width=pref_len).join(help_lines) 

print('\nHint:' + out_str + '\n') 

 

from cmd2 import readline_lib 

readline_lib.rl_forced_update_display() 

 

# noinspection PyUnusedLocal 

@staticmethod 

def basic_complete(text: str, line: str, begidx: int, endidx: int, match_against: List[str]) -> List[str]: 

""" 

Performs tab completion against a list 

 

:param text: str - the string prefix we are attempting to match (all returned matches must begin with it) 

:param line: str - the current input line with leading whitespace removed 

:param begidx: int - the beginning index of the prefix text 

:param endidx: int - the ending index of the prefix text 

:param match_against: Collection - the list being matched against 

:return: List[str] - a list of possible tab completions 

""" 

return [cur_match for cur_match in match_against if cur_match.startswith(text)] 

 

 

class ACHelpFormatter(argparse.HelpFormatter): 

"""Custom help formatter to configure ordering of help text""" 

 

def _format_usage(self, usage, actions, groups, prefix): 

if prefix is None: 

prefix = _('Usage: ') 

 

# if usage is specified, use that 

if usage is not None: 

usage %= dict(prog=self._prog) 

 

# if no optionals or positionals are available, usage is just prog 

elif usage is None and not actions: 

usage = '%(prog)s' % dict(prog=self._prog) 

 

# if optionals and positionals are available, calculate usage 

elif usage is None: 

prog = '%(prog)s' % dict(prog=self._prog) 

 

# split optionals from positionals 

optionals = [] 

required_options = [] 

positionals = [] 

for action in actions: 

if action.option_strings: 

if action.required: 

required_options.append(action) 

else: 

optionals.append(action) 

else: 

positionals.append(action) 

 

# build full usage string 

format = self._format_actions_usage 

action_usage = format(positionals + required_options + optionals, groups) 

usage = ' '.join([s for s in [prog, action_usage] if s]) 

 

# wrap the usage parts if it's too long 

text_width = self._width - self._current_indent 

if len(prefix) + len(usage) > text_width: 

 

# break usage into wrappable parts 

part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' 

opt_usage = format(optionals, groups) 

pos_usage = format(positionals, groups) 

req_usage = format(required_options, groups) 

opt_parts = _re.findall(part_regexp, opt_usage) 

pos_parts = _re.findall(part_regexp, pos_usage) 

req_parts = _re.findall(part_regexp, req_usage) 

assert ' '.join(opt_parts) == opt_usage 

assert ' '.join(pos_parts) == pos_usage 

assert ' '.join(req_parts) == req_usage 

 

# helper for wrapping lines 

def get_lines(parts, indent, prefix=None): 

lines = [] 

line = [] 

if prefix is not None: 

line_len = len(prefix) - 1 

else: 

line_len = len(indent) - 1 

for part in parts: 

if line_len + 1 + len(part) > text_width and line: 

lines.append(indent + ' '.join(line)) 

line = [] 

line_len = len(indent) - 1 

line.append(part) 

line_len += len(part) + 1 

if line: 

lines.append(indent + ' '.join(line)) 

if prefix is not None: 

lines[0] = lines[0][len(indent):] 

return lines 

 

# if prog is short, follow it with optionals or positionals 

if len(prefix) + len(prog) <= 0.75 * text_width: 

indent = ' ' * (len(prefix) + len(prog) + 1) 

if opt_parts: 

lines = get_lines([prog] + pos_parts, indent, prefix) 

lines.extend(get_lines(req_parts, indent)) 

lines.extend(get_lines(opt_parts, indent)) 

elif pos_parts: 

lines = get_lines([prog] + pos_parts, indent, prefix) 

lines.extend(get_lines(req_parts, indent)) 

else: 

lines = [prog] 

 

# if prog is long, put it on its own line 

else: 

indent = ' ' * len(prefix) 

parts = pos_parts + req_parts + opt_parts 

lines = get_lines(parts, indent) 

if len(lines) > 1: 

lines = [] 

lines.extend(get_lines(pos_parts, indent)) 

lines.extend(get_lines(req_parts, indent)) 

lines.extend(get_lines(opt_parts, indent)) 

lines = [prog] + lines 

 

# join lines into usage 

usage = '\n'.join(lines) 

 

# prefix with 'usage:' 

return '%s%s\n\n' % (prefix, usage) 

 

def _format_action_invocation(self, action): 

if not action.option_strings: 

default = self._get_default_metavar_for_positional(action) 

metavar, = self._metavar_formatter(action, default)(1) 

return metavar 

 

else: 

parts = [] 

 

# if the Optional doesn't take a value, format is: 

# -s, --long 

if action.nargs == 0: 

parts.extend(action.option_strings) 

return ', '.join(parts) 

 

# if the Optional takes a value, format is: 

# -s ARGS, --long ARGS 

else: 

default = self._get_default_metavar_for_optional(action) 

args_string = self._format_args(action, default) 

 

# for option_string in action.option_strings: 

# parts.append('%s %s' % (option_string, args_string)) 

 

return ', '.join(action.option_strings) + ' ' + args_string 

 

def _format_args(self, action, default_metavar): 

get_metavar = self._metavar_formatter(action, default_metavar) 

if isinstance(action, _RangeAction) and \ 

action.nargs_min is not None and action.nargs_max is not None: 

result = '{}{{{}..{}}}'.format('%s' % get_metavar(1), action.nargs_min, action.nargs_max) 

 

elif action.nargs is None: 

result = '%s' % get_metavar(1) 

elif action.nargs == OPTIONAL: 

result = '[%s]' % get_metavar(1) 

elif action.nargs == ZERO_OR_MORE: 

result = '[%s [...]]' % get_metavar(1) 

elif action.nargs == ONE_OR_MORE: 

result = '%s [...]' % get_metavar(1) 

elif action.nargs == REMAINDER: 

result = '...' 

elif action.nargs == PARSER: 

result = '%s ...' % get_metavar(1) 

else: 

formats = ['%s' for _ in range(action.nargs)] 

result = ' '.join(formats) % get_metavar(action.nargs) 

return result 

 

def _metavar_formatter(self, action, default_metavar): 

if action.metavar is not None: 

result = action.metavar 

elif action.choices is not None: 

choice_strs = [str(choice) for choice in action.choices] 

result = '{%s}' % ', '.join(choice_strs) 

else: 

result = default_metavar 

 

def format(tuple_size): 

if isinstance(result, tuple): 

return result 

else: 

return (result, ) * tuple_size 

return format 

 

def _split_lines(self, text, width): 

return text.splitlines() 

 

 

class ACArgumentParser(argparse.ArgumentParser): 

"""Custom argparse class to override error method to change default help text.""" 

 

def __init__(self, 

prog=None, 

usage=None, 

description=None, 

epilog=None, 

parents=[], 

formatter_class=ACHelpFormatter, 

prefix_chars='-', 

fromfile_prefix_chars=None, 

argument_default=None, 

conflict_handler='error', 

add_help=True, 

allow_abbrev=True): 

 

super().__init__(prog=prog, 

usage=usage, 

description=description, 

epilog=epilog, 

parents=parents, 

formatter_class=formatter_class, 

prefix_chars=prefix_chars, 

fromfile_prefix_chars=fromfile_prefix_chars, 

argument_default=argument_default, 

conflict_handler=conflict_handler, 

add_help=add_help, 

allow_abbrev=allow_abbrev) 

register_custom_actions(self) 

 

self._custom_error_message = '' 

 

def set_custom_message(self, custom_message=''): 

""" 

Allows an error message override to the error() function, useful when forcing a 

re-parse of arguments with newly required parameters 

""" 

self._custom_error_message = custom_message 

 

def error(self, message): 

"""Custom error override.""" 

if len(self._custom_error_message) > 0: 

message = self._custom_error_message 

self._custom_error_message = '' 

 

lines = message.split('\n') 

linum = 0 

formatted_message = '' 

for line in lines: 

if linum == 0: 

formatted_message = 'Error: ' + line 

else: 

formatted_message += '\n ' + line 

linum += 1 

 

# sys.stderr.write(Fore.LIGHTRED_EX + '{}\n\n'.format(formatted_message) + Fore.RESET) 

sys.stderr.write('{}\n\n'.format(formatted_message)) 

self.print_help() 

sys.exit(1) 

 

def format_help(self): 

"""Copy of format_help() from argparse.ArgumentParser with tweaks to separately display required parameters""" 

formatter = self._get_formatter() 

 

# usage 

formatter.add_usage(self.usage, self._actions, 

self._mutually_exclusive_groups) 

 

# description 

formatter.add_text(self.description) 

 

# positionals, optionals and user-defined groups 

for action_group in self._action_groups: 

if action_group.title == 'optional arguments': 

# check if the arguments are required, group accordingly 

req_args = [] 

opt_args = [] 

for action in action_group._group_actions: 

if action.required: 

req_args.append(action) 

else: 

opt_args.append(action) 

 

# separately display required arguments 

formatter.start_section('required arguments') 

formatter.add_text(action_group.description) 

formatter.add_arguments(req_args) 

formatter.end_section() 

 

# now display truly optional arguments 

formatter.start_section(action_group.title) 

formatter.add_text(action_group.description) 

formatter.add_arguments(opt_args) 

formatter.end_section() 

else: 

formatter.start_section(action_group.title) 

formatter.add_text(action_group.description) 

formatter.add_arguments(action_group._group_actions) 

formatter.end_section() 

 

# epilog 

formatter.add_text(self.epilog) 

 

# determine help from format above 

return formatter.format_help() 

 

def _get_nargs_pattern(self, action): 

# Override _get_nargs_pattern behavior to use the nargs ranges provided by AutoCompleter 

if isinstance(action, _RangeAction) and \ 

action.nargs_min is not None and action.nargs_max is not None: 

nargs_pattern = '(-*A{{{},{}}}-*)'.format(action.nargs_min, action.nargs_max) 

 

# if this is an optional action, -- is not allowed 

if action.option_strings: 

nargs_pattern = nargs_pattern.replace('-*', '') 

nargs_pattern = nargs_pattern.replace('-', '') 

return nargs_pattern 

return super(ACArgumentParser, self)._get_nargs_pattern(action) 

 

def _match_argument(self, action, arg_strings_pattern): 

# match the pattern for this action to the arg strings 

nargs_pattern = self._get_nargs_pattern(action) 

match = _re.match(nargs_pattern, arg_strings_pattern) 

 

# raise an exception if we weren't able to find a match 

if match is None: 

if isinstance(action, _RangeAction) and \ 

action.nargs_min is not None and action.nargs_max is not None: 

raise ArgumentError(action, 

'Expected between {} and {} arguments'.format(action.nargs_min, action.nargs_max)) 

 

return super(ACArgumentParser, self)._match_argument(action, arg_strings_pattern)