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 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
@@ -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
+ class Util
3
+ def self.slugify(str)
4
+ str.gsub(' ', '_')
5
+ .gsub(/[^\w_]/, '_')
6
+ .gsub(/_+/, '_')
7
+ .gsub(/_$/, '')
8
+ .gsub(/^_/, '')
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3 @@
1
+ module DocoptCompgen
2
+ VERSION = '1.0.0'
3
+ 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: []