docopt-compgen 1.0.0

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