clamp-completer 0.1.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: 801dce4fae329b01ac6efe2a99e47a0bd6e7d1be310347c8abccfef53b7be900
4
+ data.tar.gz: 67f5db2a8d3d7bda3939523c2543435333a38811e93fcf679e20d0d9dfe66093
5
+ SHA512:
6
+ metadata.gz: 875e58f9bb107cfb1f66696a9e70cde324a5deabfed64494be63c71658eb0893df6ea1ec63d92bba0fd5d22277fda3cc9c00dbf58a0ed5f1581793ab1a9e2d9e
7
+ data.tar.gz: 07750b2fe30a8136eeb06cb6d19088e9848aa56915f34e5db2abaaadcfa2aa5ce76827dd5d91dcd4db2ec54c8145dab16f02e3d7245511bcc959f9988374c6da
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ [![Build Status](https://travis-ci.org/kontena/clamp-completer.svg?branch=master)](https://travis-ci.org/kontena/clamp-completer)
2
+ [![Gem Version](https://badge.fury.io/rb/clamp-completer)](https://badge.fury.io/rb/clamp-completer)
3
+
4
+ # Clamp::Completer
5
+
6
+ Automatically generate shell auto-completion scripts for Ruby command-line tools built using the [clamp](https://github.com/mdub/clamp) gem.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'clamp-completer'
14
+ ```
15
+
16
+ Or install it yourself as:
17
+
18
+ ```
19
+ $ gem install clamp-completer
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ Require the `clamp/completer` and in your application's root command, add a subcommand:
25
+
26
+ ```ruby
27
+ require 'clamp/completer'
28
+
29
+ Clamp do
30
+ subcommand "complete", "shell autocompletions", Clamp::Completer.new(self)
31
+ end
32
+ ```
33
+
34
+ This will add a `complete` subcommand:
35
+
36
+ ```
37
+ $ your_app complete zsh
38
+ $ your_app complete bash
39
+ ```
40
+
41
+ You can redirect the output to a static file or load the output directly into the running environment:
42
+
43
+ ```
44
+ # zsh / bash:
45
+ $ source <(your_app complete)
46
+ # or for the macOs preinstalled bash version:
47
+ $ source /dev/stdin <<<"$(your_app complete bash)"
48
+ ```
49
+
50
+ ### Customizing completions
51
+
52
+ Currently, subcommand completions and flag-type options defined through `option '--debug', :flag, 'enable debug'` should work correctly out-of-the-box. For options that take parameters,
53
+ such as file paths or a predefined set of words, you can define methods on your command classes that will be used to determine how the values should be completed:
54
+
55
+ ```ruby
56
+ Clamp do
57
+ option '--config', 'YAML_FILE', "configuration YAML file path"
58
+
59
+ def complete_yaml_file # name derived from the YAML_FILE argument description
60
+ { glob: '*.yml' }
61
+ end
62
+ end
63
+ ```
64
+
65
+ ```ruby
66
+ Clamp do
67
+ option '--role', 'ROLE_NAME', "node role"
68
+
69
+ def complete_role # name derived from the attribute name
70
+ "master worker" # will add "master" and "worker" as completion responses when you do: your_app --role <tab>
71
+ end
72
+ end
73
+ ```
74
+
75
+ #### Completion method response types
76
+
77
+ ##### String
78
+
79
+ A space separated string of possible values for the option
80
+
81
+ ##### Symbol
82
+
83
+ Currently known symbols:
84
+
85
+ * `:dirs` will complete directory names
86
+ * `:files` will complete file names
87
+ * `:hosts` will complete known host names
88
+
89
+ ##### Hash
90
+
91
+ Much like the Symbols, but returned in Hash format to enable passing options:
92
+
93
+ * `{ glob: '*.yml' }` will complete files endinging with `.yml`
94
+ * `{ command: 'cut -d':' -f1 /etc/passwd' }` will run a command to get completion candidates, in this case the usernames from `/etc/password`
95
+
96
+ ## Contributing
97
+
98
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/clamp-completer.
@@ -0,0 +1,24 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "clamp/completer/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "clamp-completer"
8
+ spec.version = Clamp::Completer::VERSION
9
+ spec.authors = ["Kontena, Inc"]
10
+ spec.email = ["info@kontena.io"]
11
+
12
+ spec.summary = %q{Automatically generate shell auto-completion scripts for clamp based cli tools}
13
+ spec.homepage = "https://github.com/kontena/clamp-completer"
14
+
15
+ spec.files = Dir["lib/**/*.{rb,erb}", "README.md", "LICENSE", "clamp-completer.gemspec"]
16
+
17
+ spec.require_paths = ["lib"]
18
+
19
+ spec.add_runtime_dependency "clamp"
20
+
21
+ spec.add_development_dependency "bundler", ">= 1.17"
22
+ spec.add_development_dependency "rspec", "~> 3.0"
23
+ spec.add_development_dependency "rubocop", "~> 0.62"
24
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "clamp/completer/version"
4
+ require 'shellwords'
5
+
6
+ module Clamp
7
+ module Completer
8
+ module Builder
9
+ autoload :ERB, 'erb'
10
+ autoload :OpenStruct, 'ostruct'
11
+
12
+ class Iterator
13
+ def initialize(root)
14
+ @root = root
15
+ end
16
+
17
+ def recurse(base = [], &block)
18
+ if @root.has_subcommands?
19
+ @root.recognised_subcommands.each do |sc|
20
+ sc.names.each do |name|
21
+ Iterator.new(sc.subcommand_class).recurse(base + [name], &block)
22
+ end
23
+ end
24
+ end
25
+ yield(base, @root) unless base.empty?
26
+ end
27
+ end
28
+
29
+ def root
30
+ self.class.root_command
31
+ end
32
+
33
+ def template_path(shell_name)
34
+ File.join(__dir__, 'completer', 'templates', "#{shell_name}.sh.erb")
35
+ end
36
+
37
+ def wrapper
38
+ klass = Class.new(self.class)
39
+ klass.subcommand(File.basename($PROGRAM_NAME), root.description || "main", root)
40
+ klass
41
+ end
42
+
43
+ def execute
44
+ template_file = template_path(shell)
45
+ unless File.exist?(template_file)
46
+ warn "Completion generation is not supported for #{shell}, defaulting to bash"
47
+ template_file = template_path("bash")
48
+ end
49
+
50
+ puts ERB.new(File.read(template_file), nil, '%-').tap { |e| e.location = [template_file, nil] }.result(
51
+ OpenStruct.new(progname: File.basename($PROGRAM_NAME), iterator: Iterator.new(wrapper)).instance_eval { binding }
52
+ )
53
+ end
54
+ end
55
+
56
+ def self.new(root_command, inherit_from: Clamp::Command)
57
+ @klass = Class.new(inherit_from) do
58
+ include Builder
59
+
60
+ class << self
61
+ attr_accessor :root_command
62
+ end
63
+ end
64
+
65
+ @klass.parameter('[SHELL]', 'shell', environment_variable: 'SHELL', default: 'bash') do |shell|
66
+ File.basename(shell)
67
+ end
68
+
69
+ @klass.root_command = root_command
70
+
71
+ @klass.banner("To load, use: source <(#{File.basename($PROGRAM_NAME)} complete #{@klass.usage_descriptions.join(' ')})")
72
+ @klass
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,92 @@
1
+ _clamp_completer_<%= progname %>_current_subcommand() {
2
+ local ret="_${COMP_WORDS[0]}"
3
+ for ((i=1; i<$COMP_CWORD; i++)); do
4
+ if [[ ! $COMP_WORDS[$i] == -* ]]; then
5
+ declare -fF -- "${ret}_${COMP_WORDS[$i]}" >/dev/null && ret="${ret}_${COMP_WORDS[$i]}"
6
+ fi
7
+ done
8
+
9
+ if [ -n "$ret" ]; then
10
+ RET="${ret}"
11
+ return 0
12
+ else
13
+ return 1
14
+ fi
15
+ }
16
+
17
+ _clamp_completer_<%= progname %>_nospace() {
18
+ command -v compopt &> /dev/null && compopt -o nospace
19
+ }
20
+
21
+ <%- iterator.recurse do |invocation_path, command_class| -%>
22
+ _<%= invocation_path.join('_') %>() {
23
+ local cur prev subcmd
24
+ COMPREPLY=()
25
+ cur="${COMP_WORDS[COMP_CWORD]}"
26
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
27
+ _clamp_completer_<%= progname %>_current_subcommand && subcmd="$RET"
28
+
29
+ if [ "$subcmd" = "${FUNCNAME[0]}${funcstack[1]}" ]; then
30
+ <%- visible_opts = command_class.recognised_options.reject(&:hidden?) -%>
31
+ if [[ ${cur} == -* ]] ; then
32
+ COMPREPLY=( $(compgen -W "<%= visible_opts.map(&:long_switch).join(' ') %>" -- ${cur}) )
33
+ return 0
34
+ fi
35
+
36
+ <%- visible_non_flag_opts = visible_opts.reject { |o| o.type == :flag } -%>
37
+ <%- unless visible_non_flag_opts.empty? -%>
38
+ <%- instance = command_class.new(invocation_path) -%>
39
+ case "$prev" in
40
+ <%- visible_non_flag_opts.each do |option| -%>
41
+ <%= option.switches.join('|') %>)
42
+ COMPREPLY=()
43
+ <%- if instance.respond_to?("complete_#{option.attribute_name}")
44
+ reply = instance.send("complete_#{option.attribute_name}")
45
+ else
46
+ cleaned_type = "complete_#{option.type.downcase.delete_prefix('[').split(' ').first}"
47
+ if instance.respond_to?(cleaned_type)
48
+ reply = instance.send(cleaned_type)
49
+ end
50
+ end -%>
51
+ <%- [reply].compact.each do |reply| -%>
52
+ <%= case reply
53
+ when String
54
+ "COMPREPLY+=( $(compgen -W \"#{reply}\" -- \"${cur}\") );"
55
+ when Symbol
56
+ case reply
57
+ when :dirs
58
+ "COMPREPLY+=( $(compgen -o nospace -d -S \"/\" -- \"${cur}\") ); _clamp_completer_#{ progname }_nospace;"
59
+ when :hosts
60
+ "COMPREPLY+=( $(compgen -A hostname -- \"${cur}\") );"
61
+ when :files
62
+ "COMPREPLY+=( $(compgen -o filenames -f -- \"${cur}\") );"
63
+ end
64
+ when Hash
65
+ case reply.first.first
66
+ when :glob
67
+ "COMPREPLY+=( $(compgen -o filenames -f -G \"#{reply.first.last}\" -- \"${cur}\") );"
68
+ when :command
69
+ "COMPREPLY+=( $(compgen -C \"#{reply.first.last}\" -- \"${cur}\") );"
70
+ end
71
+ else
72
+ "COMPREPLY+=( $(compgen -o bashdefault -- \"${cur}\") );"
73
+ end %>
74
+ <%- end -%>
75
+ return 0
76
+ ;;
77
+ <%- end -%>
78
+ esac
79
+ <%- end -%>
80
+
81
+ <%- if command_class.has_subcommands? -%>
82
+ COMPREPLY=( $(compgen -W "<%= command_class.recognised_subcommands.flat_map(&:names).join(' ') %>" -- ${cur}) )
83
+ <%- else -%>
84
+ COMPREPLY=()
85
+ <%- end -%>
86
+ elif [ -n "$subcmd" ]; then
87
+ eval "$subcmd"
88
+ fi
89
+ }
90
+
91
+ <%- end -%>
92
+ complete -F _<%= progname %> <%= progname %>
@@ -0,0 +1,94 @@
1
+ #compdef <%= progname %>
2
+
3
+ <%- iterator.recurse do |invocation_path, command_class| -%>
4
+ <%- if command_class.has_subcommands? -%>
5
+ _<%= invocation_path.join('_') %>_subcommands() {
6
+ local subcommands=( <%= command_class.recognised_subcommands.map { |sc| "'#{sc.names.first}:#{sc.description.gsub(/'\$\\/, '\\\1') }'" }.join(' ') %> )
7
+ _describe 'subcommands' subcommands && return 0
8
+ return 1
9
+ }
10
+
11
+ <%- else -%>
12
+ _<%= invocation_path.join('_') %>_subcommands() {
13
+ return 1
14
+ }
15
+
16
+ <%- end -%>
17
+
18
+ _<%= invocation_path.join('_') %>() {
19
+ local curcontext="$curcontext" state state_descr line expl
20
+ local ret=1
21
+
22
+ _arguments -C : \
23
+ '1:subcommands:_<%= invocation_path.join('_') %>_subcommands' \
24
+ <%- command_class.recognised_options.each do |opt| -%>
25
+ <%- if opt.type == :flag -%>
26
+ <%- if opt.long_switch.include?('[no-]') -%>
27
+ '<%= opt.long_switch.gsub('[no-]', 'no-') %>[(disable) <%= opt.description.gsub(/\[|\]|:/, '\\\1') %>]' \
28
+ '<%= opt.long_switch.gsub('[no-]', '') %>[(enable) <%= opt.description.gsub(/\[|\]|:/, '\\\1') %>]' \
29
+ <%- else -%>
30
+ '<%= opt.long_switch %>[<%= opt.description.gsub(/\[|\]|:/, '\\\1') %>]' \
31
+ <%- end -%>
32
+ <%- else
33
+ instance = command_class.new(invocation_path)
34
+ if instance.respond_to?("complete_#{opt.attribute_name}")
35
+ reply = instance.send("complete_#{opt.attribute_name}")
36
+ else
37
+ cleaned_type = "complete_#{opt.type.downcase.delete_prefix('[').split(' ').first}"
38
+ if instance.respond_to?(cleaned_type)
39
+ reply = instance.send(cleaned_type)
40
+ end
41
+ end
42
+ result=[]
43
+ [reply].compact.each do |reply|
44
+ case reply
45
+ when String
46
+ result << "(#{reply})"
47
+ when Symbol
48
+ case reply
49
+ when :dirs
50
+ result << "_path_files -/"
51
+ when :hosts
52
+ when :files
53
+ result << "_path_files -f"
54
+ end
55
+ when Hash
56
+ case reply.first.first
57
+ when :glob
58
+ result << "_path_files -f -g \"#{reply.first.last}\""
59
+ when :command
60
+ result << "(${(k)commands})"
61
+ end
62
+ else
63
+ end
64
+ -%>
65
+ <%- if opt.long_switch.include?('[no-]') -%>
66
+ '<%= opt.long_switch.gsub('[no-]', 'no-') %>:(disable) <%= opt.description.gsub(/\[|\]|:/, '\\\1') %>:<%= result.first %>' \
67
+ '<%= opt.long_switch.gsub('[no-]', '') %>:(enable) <%= opt.description.gsub(/\[|\]|:/, '\\\1') %>:<%= result.first %>' \
68
+ <%- else -%>
69
+ '<%= opt.long_switch %>:<%= opt.description.gsub(/\[|\]|:/, '\\\1') %>:<%= result.first %>' \
70
+ <%- end -%>
71
+ <%- end -%>
72
+ <%- end -%>
73
+ <%- end -%>
74
+ '*::options:->options' && return 0
75
+
76
+ case "$state" in
77
+ command) _<%= invocation_path.join('_') %>_subcommands && return 0 ;;
78
+ options)
79
+ local command="${line[1]}"
80
+ curcontext="${curcontext%:*:*}:<%= invocation_path.join('-') %>-${command}"
81
+
82
+ line=(${line:1})
83
+ local completion_func="_<%= invocation_path.join('_') %>_${command//-/_}"
84
+ _call_function ret "${completion_func}" && return ret
85
+
86
+ _message "a completion function is not defined for ${command}"
87
+ return 1
88
+ ;;
89
+ esac
90
+ }
91
+
92
+ <%- end -%>
93
+
94
+ compdef _<%= progname %> <%= progname %>
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Clamp
4
+ module Completer
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clamp-completer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kontena, Inc
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: clamp
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '1.17'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.17'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.62'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.62'
69
+ description:
70
+ email:
71
+ - info@kontena.io
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - README.md
77
+ - clamp-completer.gemspec
78
+ - lib/clamp/completer.rb
79
+ - lib/clamp/completer/templates/bash.sh.erb
80
+ - lib/clamp/completer/templates/zsh.sh.erb
81
+ - lib/clamp/completer/version.rb
82
+ homepage: https://github.com/kontena/clamp-completer
83
+ licenses: []
84
+ metadata: {}
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubygems_version: 3.0.2
101
+ signing_key:
102
+ specification_version: 4
103
+ summary: Automatically generate shell auto-completion scripts for clamp based cli
104
+ tools
105
+ test_files: []