docopt-compgen 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/docopt-compgen +65 -0
- data/lib/docopt-compgen/generator.rb +128 -0
- data/lib/docopt-compgen/node.rb +37 -0
- data/lib/docopt-compgen/parser.rb +81 -0
- data/lib/docopt-compgen/util.rb +11 -0
- data/lib/docopt-compgen/version.rb +3 -0
- data/lib/docopt-compgen.rb +11 -0
- metadata +92 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b3f524e54bf6f9520b41f0e64f2cd710d64a87241d7b0780fc2421655983eb1f
|
4
|
+
data.tar.gz: 5204ce5211e06c02871b42e05f49371c2b6e3b4731dd6669ae39a7f026167e07
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 53ae0d920fb9d37eab1298fc07d1172d4c57ffa6e607a2243cb283e84f77bdf9eb7b87d3f33c3c86701e1646d7a3137ad338eb1c2c9fb11c7ea11dc366aae103
|
7
|
+
data.tar.gz: c9935c4ca6acdb855a87d2bb663bf279c4cc6ec9df5d2c145f9479f212dbd3290d7a7b590ab8841b633532ec52acc96f1e8963712fcf5025fea81c985656b9b2
|
data/bin/docopt-compgen
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
trap(:SIGINT) { puts; exit 130 }
|
3
|
+
Signal.trap(:SIGPIPE, :SYSTEM_DEFAULT)
|
4
|
+
require 'docopt'
|
5
|
+
|
6
|
+
usage = <<~EOF
|
7
|
+
Usage:
|
8
|
+
#{File.basename($0)} [options] [<command>]
|
9
|
+
|
10
|
+
Options:
|
11
|
+
--command-name NAME Command name
|
12
|
+
If not specified then the slugified basename of <command> will be used
|
13
|
+
--namespace NAME Prefix for generated bash functions [default: cmd]
|
14
|
+
-h, --help Show help
|
15
|
+
EOF
|
16
|
+
|
17
|
+
begin
|
18
|
+
opts = Docopt.docopt(usage)
|
19
|
+
rescue Docopt::Exit
|
20
|
+
puts usage
|
21
|
+
exit 2
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'colorize'
|
25
|
+
|
26
|
+
if $stdin.tty?
|
27
|
+
command = opts['<command>']
|
28
|
+
if command.nil?
|
29
|
+
$stderr.puts 'Error: <command> is mandatory when not passing help via stdin'.red
|
30
|
+
puts usage
|
31
|
+
exit 1
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
help = `#{command} --help`
|
36
|
+
rescue => e
|
37
|
+
$stderr.puts e.to_s.red
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
else
|
41
|
+
if !opts['--command-name']
|
42
|
+
$stderr.puts 'Error: --command-name is mandatory when passing help via stdin'.red
|
43
|
+
puts usage
|
44
|
+
exit 1
|
45
|
+
end
|
46
|
+
|
47
|
+
help = $stdin.read
|
48
|
+
end
|
49
|
+
|
50
|
+
if help.nil? || help.lines.first !~ /Usage:/
|
51
|
+
$stderr.puts 'Unable to parse help output from "%s"'.red % command
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
|
55
|
+
require_relative '../lib/docopt-compgen'
|
56
|
+
|
57
|
+
parser = DocoptCompgen::Parser.new(help)
|
58
|
+
generator = DocoptCompgen::Generator.new(
|
59
|
+
command,
|
60
|
+
parser.to_node,
|
61
|
+
command_name: opts['--command-name'],
|
62
|
+
namespace: opts['--namespace'],
|
63
|
+
)
|
64
|
+
|
65
|
+
puts generator.to_s
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module DocoptCompgen
|
2
|
+
class Generator
|
3
|
+
def initialize(command, node, command_name: nil, namespace: nil)
|
4
|
+
@command = command ? File.basename(command) : command_name
|
5
|
+
@node = node
|
6
|
+
@command_name = command_name || Util.slugify(@command)
|
7
|
+
@namespace = '_' + namespace
|
8
|
+
end
|
9
|
+
|
10
|
+
def indent(str, level)
|
11
|
+
"\n" + str.strip.lines.map { |s| (' ' * level) + s }.join
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_op(node)
|
15
|
+
if node.subcommands.length > 0
|
16
|
+
'eq'
|
17
|
+
else
|
18
|
+
'ge'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def path_arguments?(arguments)
|
23
|
+
arguments.any? { |a| %w[<file> <dir> <path>].include?(a) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def path_options?(options)
|
27
|
+
options.any? { |a| %w[--file --dir --path].include?(a) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def include_files?(node)
|
31
|
+
return path_arguments?(node.arguments) || path_options?(node.options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def make_compreply(node)
|
35
|
+
words = []
|
36
|
+
|
37
|
+
if node.options.length > 0
|
38
|
+
words << node.options
|
39
|
+
end
|
40
|
+
|
41
|
+
words << node.subcommands.keys
|
42
|
+
|
43
|
+
return [include_files?(node), words.flatten.join(' ')]
|
44
|
+
end
|
45
|
+
|
46
|
+
def make(command_name, node, level)
|
47
|
+
subcommand_switch = make_subcommand_switch(
|
48
|
+
command_name,
|
49
|
+
level,
|
50
|
+
node.subcommands,
|
51
|
+
)
|
52
|
+
|
53
|
+
op = get_op(node)
|
54
|
+
include_files, words = make_compreply(node)
|
55
|
+
|
56
|
+
functions = [
|
57
|
+
make_function(
|
58
|
+
command_name,
|
59
|
+
op,
|
60
|
+
level,
|
61
|
+
words,
|
62
|
+
include_files,
|
63
|
+
subcommand_switch,
|
64
|
+
),
|
65
|
+
]
|
66
|
+
|
67
|
+
node.subcommands.each do |subcommand_name, subcommand_node|
|
68
|
+
functions << make(
|
69
|
+
'%s_%s' % [command_name, subcommand_name],
|
70
|
+
subcommand_node,
|
71
|
+
level + 1,
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
return functions.join("\n")
|
76
|
+
end
|
77
|
+
|
78
|
+
def make_function(command_name, op, level, words, include_files, subcommand_switch) # rubocop:disable Metrics/ParameterLists
|
79
|
+
files = ''
|
80
|
+
if include_files
|
81
|
+
files = indent(<<~EOF, 8)
|
82
|
+
local IFS=$'\\n' # Handle filenames with spaces.
|
83
|
+
COMPREPLY+=($(compgen -f -- "$cur"))
|
84
|
+
EOF
|
85
|
+
end
|
86
|
+
|
87
|
+
return <<~EOF
|
88
|
+
function #{@namespace}_#{command_name} {
|
89
|
+
local cur
|
90
|
+
cur="${COMP_WORDS[COMP_CWORD]}"
|
91
|
+
|
92
|
+
if [ "$COMP_CWORD" -#{op} #{level} ]; then
|
93
|
+
COMPREPLY=($(compgen -W '#{words}' -- "$cur"))#{files}#{subcommand_switch}
|
94
|
+
fi
|
95
|
+
}
|
96
|
+
EOF
|
97
|
+
end
|
98
|
+
|
99
|
+
def make_subcommand_switch(command_name, level, subcommands)
|
100
|
+
if subcommands.length == 0
|
101
|
+
return ''
|
102
|
+
end
|
103
|
+
|
104
|
+
cases = subcommands.map do |subcommand_name, _node|
|
105
|
+
"#{subcommand_name}) #{@namespace}_#{command_name}_#{subcommand_name} ;;"
|
106
|
+
end.join("\n ")
|
107
|
+
|
108
|
+
return indent(<<~EOF, 4)
|
109
|
+
else
|
110
|
+
case ${COMP_WORDS[#{level}]} in
|
111
|
+
#{cases}
|
112
|
+
esac
|
113
|
+
EOF
|
114
|
+
end
|
115
|
+
|
116
|
+
def to_s
|
117
|
+
content = make(@command_name, @node, 1)
|
118
|
+
|
119
|
+
return <<~EOF
|
120
|
+
#!/bin/bash
|
121
|
+
# shellcheck disable=SC2207
|
122
|
+
|
123
|
+
#{content}
|
124
|
+
complete -o bashdefault -o default -o filenames -F #{@namespace}_#{@command_name} #{@command}
|
125
|
+
EOF
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module DocoptCompgen
|
2
|
+
class Node
|
3
|
+
attr_reader :arguments
|
4
|
+
attr_reader :subcommands
|
5
|
+
attr_reader :parent
|
6
|
+
|
7
|
+
def initialize(parent = nil)
|
8
|
+
@parent = parent
|
9
|
+
@subcommands = {}
|
10
|
+
@options = []
|
11
|
+
@arguments = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_argument(argument)
|
15
|
+
@arguments << argument
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_option(option)
|
19
|
+
@options << option
|
20
|
+
end
|
21
|
+
|
22
|
+
def options
|
23
|
+
options = @options
|
24
|
+
if @parent
|
25
|
+
options += @parent.options
|
26
|
+
end
|
27
|
+
options.uniq
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_subcommand(name)
|
31
|
+
if @subcommands[name].nil?
|
32
|
+
@subcommands[name] = Node.new(self)
|
33
|
+
end
|
34
|
+
@subcommands[name]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module DocoptCompgen
|
2
|
+
class Parser
|
3
|
+
def initialize(help)
|
4
|
+
@help = help
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_node
|
8
|
+
build(parse_docopt, Node.new)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def parse_docopt
|
14
|
+
options = Docopt.parse_defaults(@help)
|
15
|
+
|
16
|
+
pattern = Docopt.parse_pattern(
|
17
|
+
Docopt.formal_usage(Docopt.printable_usage(@help)),
|
18
|
+
options,
|
19
|
+
)
|
20
|
+
|
21
|
+
# Options explicitly specified in subcommands cannot be used for a
|
22
|
+
# general [options] placeholder. This is necessary so that options
|
23
|
+
# can be restricted to specific subcommands while still allowing the
|
24
|
+
# remaining to be used in place of [options].
|
25
|
+
used_options = pattern.flat(Docopt::Option).uniq
|
26
|
+
pattern.flat(Docopt::AnyOptions).each do |any_options|
|
27
|
+
any_options.children = options.reject do |option|
|
28
|
+
used_options.include?(option)
|
29
|
+
end.uniq
|
30
|
+
end
|
31
|
+
|
32
|
+
return pattern
|
33
|
+
end
|
34
|
+
|
35
|
+
def build(pattern, node)
|
36
|
+
wrappers = [
|
37
|
+
Docopt::Either,
|
38
|
+
Docopt::Optional,
|
39
|
+
Docopt::OneOrMore,
|
40
|
+
Docopt::AnyOptions,
|
41
|
+
]
|
42
|
+
|
43
|
+
if wrappers.include?(pattern.class)
|
44
|
+
pattern.children.each do |child|
|
45
|
+
build(child, node)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if [Docopt::Required].include?(pattern.class)
|
50
|
+
pattern.children.each do |child|
|
51
|
+
new_node = build(child, node)
|
52
|
+
if child.is_a?(Docopt::Command)
|
53
|
+
node = new_node
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# rubocop:disable Style/SoleNestedConditional
|
59
|
+
if [Docopt::Option].include?(pattern.class)
|
60
|
+
# if pattern.short
|
61
|
+
# node.add_option(pattern.short)
|
62
|
+
# end
|
63
|
+
|
64
|
+
if pattern.long
|
65
|
+
node.add_option(pattern.long)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
# rubocop:enable Style/SoleNestedConditional
|
69
|
+
|
70
|
+
if [Docopt::Argument].include?(pattern.class)
|
71
|
+
node.add_argument(pattern.name)
|
72
|
+
end
|
73
|
+
|
74
|
+
if [Docopt::Command].include?(pattern.class)
|
75
|
+
node = node.add_subcommand(pattern.name)
|
76
|
+
end
|
77
|
+
|
78
|
+
return node
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module DocoptCompgen
|
2
|
+
def self.root_dir
|
3
|
+
File.expand_path('../..', __FILE__)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require_relative 'docopt-compgen/version'
|
8
|
+
require_relative 'docopt-compgen/util'
|
9
|
+
require_relative 'docopt-compgen/node'
|
10
|
+
require_relative 'docopt-compgen/parser'
|
11
|
+
require_relative 'docopt-compgen/generator'
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: docopt-compgen
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- crdx
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-10-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.8.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.8.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: docopt
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.6.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13.0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
executables:
|
58
|
+
- docopt-compgen
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- bin/docopt-compgen
|
63
|
+
- lib/docopt-compgen.rb
|
64
|
+
- lib/docopt-compgen/generator.rb
|
65
|
+
- lib/docopt-compgen/node.rb
|
66
|
+
- lib/docopt-compgen/parser.rb
|
67
|
+
- lib/docopt-compgen/util.rb
|
68
|
+
- lib/docopt-compgen/version.rb
|
69
|
+
homepage: https://github.com/crdx/docopt-compgen
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata: {}
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
requirements: []
|
88
|
+
rubygems_version: 3.2.21
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: docopt completion generator
|
92
|
+
test_files: []
|