usage method

String usage()

Return a string telling the user how to use your application from the command line.

Implementation

String usage() {
  List<String?> lines = [];

  if (_isNotNull(_app?.description)) {
    lines.add(_app!.description);
    lines.add('');
  }

  List<String> helpKeys = [];
  List<Group?> helpGroups = [];
  List<List<String>> helpDescriptions = [];

  final arguments =
      _mirrorParameterPairs.where((v) => _isFalse(v.argument is Command));
  final commands = _mirrorParameterPairs.where((v) => v.argument is Command);

  if (arguments.isNotEmpty) {
    for (var mpp in arguments) {
      List<String?> keys = [];

      keys.addAll(mpp.keys(_app).map((v) => v!.startsWith('-') ? v : '--$v'));
      helpKeys.add(keys.join(', '));
      helpGroups.add(mpp.group);

      List<String> helpLines = [mpp.argument.help ?? 'no help available'];

      if (mpp.argument.isRequired ?? false) {
        helpLines.add('[REQUIRED]');
      }

      String? envVar = mpp.argument.environmentVariable;
      if (_isNotBlank(envVar)) {
        helpLines.add('[Environment Variable: \$$envVar]');
      }

      helpLines.addAll(mpp.argument.additionalHelpLines);
      helpDescriptions.add(helpLines);
    }
  }

  const lineWidth = 78; // TODO: Can we get this from the terminal?
  const lineIndent = 2;
  const maxKeyLenAllowed = 25; // Will include indent
  final linePrefix = ' ' * lineIndent;

  final maxKeyLen = helpKeys.fold<int>(
      0,
      (a, b) => (b.length + lineIndent) > a &&
              (b.length + lineIndent) < maxKeyLenAllowed
          ? (b.length + lineIndent)
          : a);
  final keyPadWidth = min(maxKeyLenAllowed, maxKeyLen + 1);

  {
    void trailingHelp(Group? group) {
      if (_isNotNull(group?.afterHelp)) {
        lines.add('');
        lines.add(indent(
          hardWrap(group!.afterHelp!, lineWidth - lineIndent),
          lineIndent,
        ));
      }
    }

    Group? currentGroup;

    for (var i = 0; i < helpKeys.length; i++) {
      final thisGroup = helpGroups[i];

      if (thisGroup != currentGroup) {
        trailingHelp(currentGroup);

        if (_isNotNull(currentGroup)) {
          lines.add('');
        }

        lines.add(thisGroup!.name);

        if (_isNotNull(thisGroup.beforeHelp)) {
          lines.add(indent(
              hardWrap(thisGroup.beforeHelp!, lineWidth - lineIndent),
              lineIndent));
          lines.add('');
        }
      }

      var keyDisplay =
          linePrefix + helpKeys[i].padRight(keyPadWidth - lineIndent);

      var thisHelpDescriptions = helpDescriptions[i].join('\n');
      thisHelpDescriptions =
          hardWrap(thisHelpDescriptions, lineWidth - keyPadWidth);
      thisHelpDescriptions = indent(thisHelpDescriptions, keyPadWidth);

      if (keyDisplay.length == keyPadWidth) {
        thisHelpDescriptions =
            thisHelpDescriptions.replaceRange(0, keyPadWidth, keyDisplay);
      } else {
        lines.add(keyDisplay);
      }

      lines.add(thisHelpDescriptions);

      currentGroup = helpGroups[i] ?? currentGroup;
    }

    trailingHelp(currentGroup);
  }

  if (commands.isNotEmpty) {
    lines.add('');
    lines.add('COMMANDS');

    final maxCommandLength =
        commands.fold(0, (int a, b) => max(a, b.displayKey!.length));

    for (var mpp in commands) {
      final key = mpp.displayKey!.padRight(maxCommandLength + 1);
      final help = mpp.argument.help ?? '';
      final displayString = '$key $help';

      lines.add(indent(hardWrap(displayString, lineWidth), 2));
    }
  }

  if (_isNotNull(_app?.extendedHelp)) {
    for (final eh in _app!.extendedHelp!) {
      if (_isNull(eh.help)) {
        throw StateError('Help.help must be set');
      }

      lines.add('');

      if (_isNotNull(eh.header)) {
        lines.add(hardWrap(eh.header!, lineWidth));
        lines.add(
            indent(hardWrap(eh.help!, lineWidth - lineIndent), lineIndent));
      } else {
        lines.add(hardWrap(eh.help!, lineWidth));
      }
    }
  }

  return lines.join('\n');
}