| import 'dart:convert'; |
| import 'dart:io'; |
| |
| import 'package:path/path.dart' as pathos; |
| |
| const _binNameReplacement = '{{binName}}'; |
| const _funcNameReplacement = '{{funcName}}'; |
| |
| /* |
| * Must be at least one char. |
| * Must start with a letter or number |
| * Can contain letters, numbers, '_', '-', '.' |
| * Must end with letter or number |
| */ |
| final _binNameMatch = RegExp(r'^[a-zA-Z0-9]((\w|-|\.)*[a-zA-Z0-9])?$'); |
| |
| /* |
| * Format for unified bash and zsh completion script: |
| * https://npmjs.org/ |
| * https://github.com/isaacs/npm/blob/master/lib/utils/completion.sh |
| * |
| * Inspiration for auto-generating completion scripts: |
| * https://github.com/mklabs/node-tabtab |
| * https://github.com/mklabs/node-tabtab/blob/master/lib/completion.sh |
| */ |
| |
| String generateCompletionScript(List<String> binaryNames) { |
| if (binaryNames.isEmpty) { |
| throw ArgumentError('Provide the name of at least of one command'); |
| } |
| |
| for (final binName in binaryNames) { |
| if (!_binNameMatch.hasMatch(binName)) { |
| final msg = 'The provided name - "$binName" - is invalid\n' |
| 'It must match regex: ${_binNameMatch.pattern}'; |
| throw StateError(msg); |
| } |
| } |
| |
| final buffer = StringBuffer(); |
| |
| final prefix = |
| LineSplitter.split(_prefix).map((l) => '# $l'.trim()).join('\n'); |
| buffer..writeln(prefix)..writeln(); |
| |
| for (final binName in binaryNames) { |
| buffer.writeln(_printBinName(binName)); |
| } |
| |
| final detailLines = [ |
| 'Generated ${DateTime.now().toUtc()}', |
| ]; |
| |
| if (Platform.script.scheme == 'file') { |
| var scriptPath = Platform.script.toFilePath(); |
| scriptPath = pathos.absolute(pathos.normalize(scriptPath)); |
| |
| detailLines.add('By $scriptPath'); |
| } |
| |
| final details = detailLines.map((l) => '## $l').join('\n'); |
| buffer.write(details); |
| |
| return buffer.toString(); |
| } |
| |
| String _printBinName(String binName) { |
| final templateContents = _template.replaceAll(_binNameReplacement, binName); |
| |
| var funcName = binName.replaceAll('.', '_'); |
| funcName = '__${funcName}_completion'; |
| return templateContents.replaceAll(_funcNameReplacement, funcName); |
| } |
| |
| const _prefix = ''' |
| |
| Installation: |
| |
| Via shell config file ~/.bashrc (or ~/.zshrc) |
| |
| Append the contents to config file |
| 'source' the file in the config file |
| |
| You may also have a directory on your system that is configured |
| for completion files, such as: |
| |
| /usr/local/etc/bash_completion.d/ |
| '''; |
| |
| const _template = r''' |
| ###-begin-{{binName}}-completion-### |
| |
| if type complete &>/dev/null; then |
| {{funcName}}() { |
| local si="$IFS" |
| IFS=$'\n' COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \ |
| COMP_LINE="$COMP_LINE" \ |
| COMP_POINT="$COMP_POINT" \ |
| {{binName}} completion -- "${COMP_WORDS[@]}" \ |
| 2>/dev/null)) || return $? |
| IFS="$si" |
| } |
| complete -F {{funcName}} {{binName}} |
| elif type compdef &>/dev/null; then |
| {{funcName}}() { |
| si=$IFS |
| compadd -- $(COMP_CWORD=$((CURRENT-1)) \ |
| COMP_LINE=$BUFFER \ |
| COMP_POINT=0 \ |
| {{binName}} completion -- "${words[@]}" \ |
| 2>/dev/null) |
| IFS=$si |
| } |
| compdef {{funcName}} {{binName}} |
| elif type compctl &>/dev/null; then |
| {{funcName}}() { |
| local cword line point words si |
| read -Ac words |
| read -cn cword |
| let cword-=1 |
| read -l line |
| read -ln point |
| si="$IFS" |
| IFS=$'\n' reply=($(COMP_CWORD="$cword" \ |
| COMP_LINE="$line" \ |
| COMP_POINT="$point" \ |
| {{binName}} completion -- "${words[@]}" \ |
| 2>/dev/null)) || return $? |
| IFS="$si" |
| } |
| compctl -K {{funcName}} {{binName}} |
| fi |
| |
| ###-end-{{binName}}-completion-### |
| '''; |