docopt-compgen 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|