clamp-completer 0.1.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: 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: []