importmap 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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +6 -0
- data/Guardfile +19 -0
- data/LICENSE.txt +22 -0
- data/README.md +20 -0
- data/Rakefile +6 -0
- data/exe/importmap +14 -0
- data/importmap.gemspec +32 -0
- data/lib/importmap/autoloader.rb +22 -0
- data/lib/importmap/cli/help/completion.md +20 -0
- data/lib/importmap/cli/help/completion_script.md +3 -0
- data/lib/importmap/cli/help/pin.md +5 -0
- data/lib/importmap/cli/help.rb +11 -0
- data/lib/importmap/cli.rb +156 -0
- data/lib/importmap/command.rb +89 -0
- data/lib/importmap/completer/script.rb +8 -0
- data/lib/importmap/completer/script.sh +10 -0
- data/lib/importmap/completer.rb +158 -0
- data/lib/importmap/framework/jets_framework.rb +11 -0
- data/lib/importmap/framework/rails_framework.rb +11 -0
- data/lib/importmap/framework.rb +49 -0
- data/lib/importmap/map.rb +158 -0
- data/lib/importmap/npm.rb +124 -0
- data/lib/importmap/packager.rb +145 -0
- data/lib/importmap/reloader.rb +20 -0
- data/lib/importmap/version.rb +3 -0
- data/lib/importmap.rb +24 -0
- data/spec/cli_spec.rb +26 -0
- data/spec/spec_helper.rb +29 -0
- metadata +217 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 84dd54a2297318f246781b1acee8edd024c4ca2650a65a287269416d2dd903a3
|
4
|
+
data.tar.gz: 203d783fc05f8a442440aa17c072d8a875e11fbd16cc634406f1c348aa1f28af
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ffe2c28e6e9312108f9c0bdce4eabc1b5f8a30dc5c8428e266ed5ee7cf98d6918f48b79acf38dfea5f560dbd195a899b8c0faafa1567712d036b2bfcf9021023
|
7
|
+
data.tar.gz: 2a502c24447c2530fd3072a7f98a5f0577546e988469880a040240cb4a931a12e308d47443b6a6db54b8deadbfa9b78162b0133de06950c7550183a82a409d3a
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
guard "bundler", cmd: "bundle" do
|
2
|
+
watch("Gemfile")
|
3
|
+
watch(/^.+\.gemspec/)
|
4
|
+
end
|
5
|
+
|
6
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
7
|
+
require "guard/rspec/dsl"
|
8
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
9
|
+
|
10
|
+
# RSpec files
|
11
|
+
rspec = dsl.rspec
|
12
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
13
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
14
|
+
watch(rspec.spec_files)
|
15
|
+
|
16
|
+
# Ruby files
|
17
|
+
ruby = dsl.ruby
|
18
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
19
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) Tung Nguyen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Importmap
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/jets-responders)
|
4
|
+
|
5
|
+
[](https://www.boltops.com)
|
6
|
+
|
7
|
+
[](https://learn.boltops.com)
|
8
|
+
|
9
|
+
Importmap library that is use as a part of [importmap-jets](https://github.com/rubyonjets/importmap-jets).
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
importmap audit # Run a security audit
|
14
|
+
importmap json # Show the full importmap in json
|
15
|
+
importmap outdated # Check for outdated packages
|
16
|
+
importmap packages # Print out packages with version numbers
|
17
|
+
importmap pin [*PACKAGES] # Pin new packages
|
18
|
+
importmap unpin [*PACKAGES] # Unpin existing packages
|
19
|
+
importmap version # prints version
|
20
|
+
|
data/Rakefile
ADDED
data/exe/importmap
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Trap ^C
|
4
|
+
Signal.trap("INT") {
|
5
|
+
puts "\nCtrl-C detected. Exiting..."
|
6
|
+
sleep 0.1
|
7
|
+
exit
|
8
|
+
}
|
9
|
+
|
10
|
+
$:.unshift(File.expand_path("../../lib", __FILE__))
|
11
|
+
require "importmap"
|
12
|
+
require "importmap/cli"
|
13
|
+
|
14
|
+
Importmap::CLI.start(ARGV)
|
data/importmap.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "importmap/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "importmap"
|
8
|
+
spec.version = Importmap::VERSION
|
9
|
+
spec.authors = ["Tung Nguyen"]
|
10
|
+
spec.email = ["tongueroo@gmail.com"]
|
11
|
+
spec.summary = "Importmap Library and CLI"
|
12
|
+
spec.homepage = "https://github.com/rubyonjets/importmap"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = File.directory?('.git') ? `git ls-files`.split($/) : Dir.glob("**/*")
|
16
|
+
spec.bindir = "exe"
|
17
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activesupport"
|
22
|
+
spec.add_dependency "memoist"
|
23
|
+
spec.add_dependency "rainbow"
|
24
|
+
spec.add_dependency "thor"
|
25
|
+
spec.add_dependency "zeitwerk"
|
26
|
+
|
27
|
+
spec.add_development_dependency "bundler"
|
28
|
+
spec.add_development_dependency "byebug"
|
29
|
+
spec.add_development_dependency "cli_markdown"
|
30
|
+
spec.add_development_dependency "rake"
|
31
|
+
spec.add_development_dependency "rspec"
|
32
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "zeitwerk"
|
2
|
+
|
3
|
+
module Importmap
|
4
|
+
class Autoloader
|
5
|
+
class Inflector < Zeitwerk::Inflector
|
6
|
+
def camelize(basename, _abspath)
|
7
|
+
map = { cli: "CLI", version: "VERSION" }
|
8
|
+
map[basename.to_sym] || super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def setup
|
14
|
+
loader = Zeitwerk::Loader.new
|
15
|
+
loader.inflector = Inflector.new
|
16
|
+
lib = File.dirname(__dir__)
|
17
|
+
loader.push_dir(lib) # lib
|
18
|
+
loader.setup
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
## Examples
|
2
|
+
|
3
|
+
importmap completion
|
4
|
+
|
5
|
+
Prints words for TAB auto-completion.
|
6
|
+
|
7
|
+
importmap completion
|
8
|
+
importmap completion hello
|
9
|
+
importmap completion hello name
|
10
|
+
|
11
|
+
To enable, TAB auto-completion add the following to your profile:
|
12
|
+
|
13
|
+
eval $(importmap completion_script)
|
14
|
+
|
15
|
+
Auto-completion example usage:
|
16
|
+
|
17
|
+
importmap [TAB]
|
18
|
+
importmap hello [TAB]
|
19
|
+
importmap hello name [TAB]
|
20
|
+
importmap hello name --[TAB]
|
@@ -0,0 +1,156 @@
|
|
1
|
+
module Importmap
|
2
|
+
class CLI < Command
|
3
|
+
include Thor::Actions
|
4
|
+
|
5
|
+
desc "pin [*PACKAGES]", "Pin new packages"
|
6
|
+
option :env, type: :string, aliases: :e, default: "production"
|
7
|
+
option :from, type: :string, aliases: :f, default: "jspm"
|
8
|
+
option :download, type: :boolean, aliases: :d, default: false
|
9
|
+
def pin(*packages)
|
10
|
+
if imports = packager.import(*packages, env: options[:env], from: options[:from])
|
11
|
+
imports.each do |package, url|
|
12
|
+
if options[:download]
|
13
|
+
puts %(Pinning "#{package}" to #{packager.vendor_path}/#{package}.js via download from #{url})
|
14
|
+
packager.download(package, url)
|
15
|
+
pin = packager.vendored_pin_for(package, url)
|
16
|
+
else
|
17
|
+
puts %(Pinning "#{package}" to #{url})
|
18
|
+
pin = packager.pin_for(package, url)
|
19
|
+
end
|
20
|
+
|
21
|
+
if packager.packaged?(package)
|
22
|
+
gsub_file("config/importmap.rb", /^pin "#{package}".*$/, pin, verbose: false)
|
23
|
+
else
|
24
|
+
append_to_file("config/importmap.rb", "#{pin}\n", verbose: false)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
else
|
28
|
+
puts "Couldn't find any packages in #{packages.inspect} on #{options[:from]}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "unpin [*PACKAGES]", "Unpin existing packages"
|
33
|
+
option :env, type: :string, aliases: :e, default: "production"
|
34
|
+
option :from, type: :string, aliases: :f, default: "jspm"
|
35
|
+
option :download, type: :boolean, aliases: :d, default: false
|
36
|
+
def unpin(*packages)
|
37
|
+
if imports = packager.import(*packages, env: options[:env], from: options[:from])
|
38
|
+
imports.each do |package, url|
|
39
|
+
if packager.packaged?(package)
|
40
|
+
if options[:download]
|
41
|
+
puts %(Unpinning and removing "#{package}")
|
42
|
+
else
|
43
|
+
puts %(Unpinning "#{package}")
|
44
|
+
end
|
45
|
+
|
46
|
+
packager.remove(package)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
else
|
50
|
+
puts "Couldn't find any packages in #{packages.inspect} on #{options[:from]}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "json", "Show the full importmap in json"
|
55
|
+
def json
|
56
|
+
Importmap.framework_boot
|
57
|
+
puts Importmap.framework.application.importmap.to_json(resolver: ActionController::Base.helpers)
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "audit", "Run a security audit"
|
61
|
+
def audit
|
62
|
+
vulnerable_packages = npm.vulnerable_packages
|
63
|
+
|
64
|
+
if vulnerable_packages.any?
|
65
|
+
table = [["Package", "Severity", "Vulnerable versions", "Vulnerability"]]
|
66
|
+
vulnerable_packages.each { |p| table << [p.name, p.severity, p.vulnerable_versions, p.vulnerability] }
|
67
|
+
|
68
|
+
puts_table(table)
|
69
|
+
vulnerabilities = 'vulnerability'.pluralize(vulnerable_packages.size)
|
70
|
+
severities = vulnerable_packages.map(&:severity).tally.sort_by(&:last).reverse
|
71
|
+
.map { |severity, count| "#{count} #{severity}" }
|
72
|
+
.join(", ")
|
73
|
+
puts " #{vulnerable_packages.size} #{vulnerabilities} found: #{severities}"
|
74
|
+
|
75
|
+
exit 1
|
76
|
+
else
|
77
|
+
puts "No vulnerable packages found"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "outdated", "Check for outdated packages"
|
82
|
+
def outdated
|
83
|
+
outdated_packages = npm.outdated_packages
|
84
|
+
|
85
|
+
if outdated_packages.any?
|
86
|
+
table = [["Package", "Current", "Latest"]]
|
87
|
+
outdated_packages.each { |p| table << [p.name, p.current_version, p.latest_version || p.error] }
|
88
|
+
|
89
|
+
puts_table(table)
|
90
|
+
packages = 'package'.pluralize(outdated_packages.size)
|
91
|
+
puts " #{outdated_packages.size} outdated #{packages} found"
|
92
|
+
|
93
|
+
exit 1
|
94
|
+
else
|
95
|
+
puts "No outdated packages found"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
desc "packages", "Print out packages with version numbers"
|
100
|
+
def packages
|
101
|
+
puts npm.packages_with_versions.map { |x| x.join(' ') }
|
102
|
+
end
|
103
|
+
|
104
|
+
desc "completion *PARAMS", "Prints words for auto-completion."
|
105
|
+
long_desc Help.text(:completion)
|
106
|
+
def completion(*params)
|
107
|
+
Completer.new(CLI, *params).run
|
108
|
+
end
|
109
|
+
|
110
|
+
desc "completion_script", "Generates a script that can be eval to setup auto-completion."
|
111
|
+
long_desc Help.text(:completion_script)
|
112
|
+
def completion_script
|
113
|
+
Completer::Script.generate
|
114
|
+
end
|
115
|
+
|
116
|
+
desc "version", "prints version"
|
117
|
+
def version
|
118
|
+
puts Importmap::VERSION
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
def packager
|
123
|
+
@packager ||= Importmap::Packager.new
|
124
|
+
end
|
125
|
+
|
126
|
+
def npm
|
127
|
+
@npm ||= Importmap::Npm.new
|
128
|
+
end
|
129
|
+
|
130
|
+
def remove_line_from_file(path, pattern)
|
131
|
+
path = File.expand_path(path, destination_root)
|
132
|
+
|
133
|
+
all_lines = File.readlines(path)
|
134
|
+
with_lines_removed = all_lines.select { |line| line !~ pattern }
|
135
|
+
|
136
|
+
File.open(path, "w") do |file|
|
137
|
+
with_lines_removed.each { |line| file.write(line) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def puts_table(array)
|
142
|
+
column_sizes = array.reduce([]) do |lengths, row|
|
143
|
+
row.each_with_index.map{ |iterand, index| [lengths[index] || 0, iterand.to_s.length].max }
|
144
|
+
end
|
145
|
+
|
146
|
+
puts head = "+" + (column_sizes.map { |s| "-" * (s + 2) }.join('+')) + '+'
|
147
|
+
array.each_with_index do |row, row_number|
|
148
|
+
row = row.fill(nil, row.size..(column_sizes.size - 1))
|
149
|
+
row = row.each_with_index.map { |v, i| v.to_s + " " * (column_sizes[i] - v.to_s.length) }
|
150
|
+
puts "| " + row.join(" | ") + " |"
|
151
|
+
puts head if row_number == 0
|
152
|
+
end
|
153
|
+
puts head
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
# Override thor's long_desc identation behavior
|
4
|
+
# https://github.com/erikhuda/thor/issues/398
|
5
|
+
class Thor
|
6
|
+
module Shell
|
7
|
+
class Basic
|
8
|
+
def print_wrapped(message, options = {})
|
9
|
+
message = "\n#{message}" unless message[0] == "\n"
|
10
|
+
stdout.puts message
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Importmap
|
17
|
+
class Command < Thor
|
18
|
+
class << self
|
19
|
+
def dispatch(m, args, options, config)
|
20
|
+
# Allow calling for help via:
|
21
|
+
# importmap command help
|
22
|
+
# importmap command -h
|
23
|
+
# importmap command --help
|
24
|
+
# importmap command -D
|
25
|
+
#
|
26
|
+
# as well thor's normal way:
|
27
|
+
#
|
28
|
+
# importmap help command
|
29
|
+
help_flags = Thor::HELP_MAPPINGS + ["help"]
|
30
|
+
if args.length > 1 && !(args & help_flags).empty?
|
31
|
+
args -= help_flags
|
32
|
+
args.insert(-2, "help")
|
33
|
+
end
|
34
|
+
|
35
|
+
# importmap version
|
36
|
+
# importmap --version
|
37
|
+
# importmap -v
|
38
|
+
version_flags = ["--version", "-v"]
|
39
|
+
if args.length == 1 && !(args & version_flags).empty?
|
40
|
+
args = ["version"]
|
41
|
+
end
|
42
|
+
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
# Override command_help to include the description at the top of the
|
47
|
+
# long_description.
|
48
|
+
def command_help(shell, command_name)
|
49
|
+
meth = normalize_command_name(command_name)
|
50
|
+
command = all_commands[meth]
|
51
|
+
alter_command_description(command)
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
def alter_command_description(command)
|
56
|
+
return unless command
|
57
|
+
|
58
|
+
# Add description to beginning of long_description
|
59
|
+
long_desc = if command.long_description
|
60
|
+
"#{command.description}\n\n#{command.long_description}"
|
61
|
+
else
|
62
|
+
command.description
|
63
|
+
end
|
64
|
+
|
65
|
+
# add reference url to end of the long_description
|
66
|
+
unless website.empty?
|
67
|
+
full_command = [command.ancestor_name, command.name].compact.join('-')
|
68
|
+
url = "#{website}/reference/importmap-#{full_command}"
|
69
|
+
long_desc += "\n\nHelp also available at: #{url}"
|
70
|
+
end
|
71
|
+
|
72
|
+
command.long_description = long_desc
|
73
|
+
end
|
74
|
+
private :alter_command_description
|
75
|
+
|
76
|
+
# meant to be overriden
|
77
|
+
def website
|
78
|
+
""
|
79
|
+
end
|
80
|
+
|
81
|
+
# https://github.com/erikhuda/thor/issues/244
|
82
|
+
# Deprecation warning: Thor exit with status 0 on errors. To keep this behavior, you must define `exit_on_failure?` in `Lono::CLI`
|
83
|
+
# You can silence deprecations warning by setting the environment variable THOR_SILENCE_DEPRECATION.
|
84
|
+
def exit_on_failure?
|
85
|
+
true
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
_importmap() {
|
2
|
+
COMPREPLY=()
|
3
|
+
local word="${COMP_WORDS[COMP_CWORD]}"
|
4
|
+
local words=("${COMP_WORDS[@]}")
|
5
|
+
unset words[0]
|
6
|
+
local completion=$(importmap completion ${words[@]})
|
7
|
+
COMPREPLY=( $(compgen -W "$completion" -- "$word") )
|
8
|
+
}
|
9
|
+
|
10
|
+
complete -F _importmap importmap
|
@@ -0,0 +1,158 @@
|
|
1
|
+
=begin
|
2
|
+
Code Explanation:
|
3
|
+
|
4
|
+
There are 3 types of things to auto-complete:
|
5
|
+
|
6
|
+
1. command: the command itself
|
7
|
+
2. parameters: command parameters.
|
8
|
+
3. options: command options
|
9
|
+
|
10
|
+
Here's an example:
|
11
|
+
|
12
|
+
mycli hello name --from me
|
13
|
+
|
14
|
+
* command: hello
|
15
|
+
* parameters: name
|
16
|
+
* option: --from
|
17
|
+
|
18
|
+
When command parameters are done processing, the remaining completion words will be options. We can tell that the command params are completed based on the method arity.
|
19
|
+
|
20
|
+
## Arity
|
21
|
+
|
22
|
+
For example, say you had a method for a CLI command with the following form:
|
23
|
+
|
24
|
+
CLI COMMAND service count --cluster development
|
25
|
+
|
26
|
+
It's equivalent ruby method:
|
27
|
+
|
28
|
+
scale(service, count) = has an arity of 2
|
29
|
+
|
30
|
+
So typing:
|
31
|
+
|
32
|
+
CLI COMMAND service count [TAB] # there are 3 parameters including the "scale" command according to Thor's CLI processing.
|
33
|
+
|
34
|
+
So the completion should only show options, something like this:
|
35
|
+
|
36
|
+
--noop --verbose --cluster
|
37
|
+
|
38
|
+
## Splat Arguments
|
39
|
+
|
40
|
+
When the ruby method has a splat argument, it's arity is negative. Here are some example methods and their arities.
|
41
|
+
|
42
|
+
ship(service) = 1
|
43
|
+
scale(service, count) = 2
|
44
|
+
foo(example, *rest) = -2
|
45
|
+
|
46
|
+
Fortunately, negative and positive arity values are processed the same way. So we take simply take the absolute value of the arity and process it the same.
|
47
|
+
|
48
|
+
Here are some test cases, hit TAB after typing the command:
|
49
|
+
|
50
|
+
importmap completion
|
51
|
+
importmap completion hello
|
52
|
+
importmap completion hello name
|
53
|
+
importmap completion hello name --
|
54
|
+
importmap completion hello name --noop
|
55
|
+
|
56
|
+
importmap completion
|
57
|
+
importmap completion sub:goodbye
|
58
|
+
importmap completion sub:goodbye name
|
59
|
+
|
60
|
+
## Subcommands and Thor::Group Registered Commands
|
61
|
+
|
62
|
+
Sometimes the commands are not simple thor commands but are subcommands or Thor::Group commands. A good specific example is the CLI COMMAND.
|
63
|
+
|
64
|
+
* regular command: CLI COMMAND
|
65
|
+
* subcommand: CLI COMMAND
|
66
|
+
* Thor::Group command: CLI COMMAND
|
67
|
+
|
68
|
+
Auto-completion accounts for each of these type of commands.
|
69
|
+
=end
|
70
|
+
module Importmap
|
71
|
+
class Completer
|
72
|
+
def initialize(command_class, *params)
|
73
|
+
@params = params
|
74
|
+
@current_command = @params[0]
|
75
|
+
@command_class = command_class # CLI initiall
|
76
|
+
end
|
77
|
+
|
78
|
+
def run
|
79
|
+
if subcommand?(@current_command)
|
80
|
+
subcommand_class = @command_class.subcommand_classes[@current_command]
|
81
|
+
@params.shift # destructive
|
82
|
+
Completer.new(subcommand_class, *@params).run # recursively use subcommand
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
# full command has been found!
|
87
|
+
unless found?(@current_command)
|
88
|
+
puts all_commands
|
89
|
+
return
|
90
|
+
end
|
91
|
+
|
92
|
+
# will only get to here if command aws found (above)
|
93
|
+
arity = @command_class.instance_method(@current_command).arity.abs
|
94
|
+
if @params.size > arity or thor_group_command?
|
95
|
+
puts options_completion
|
96
|
+
else
|
97
|
+
puts params_completion
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def subcommand?(command)
|
102
|
+
@command_class.subcommands.include?(command)
|
103
|
+
end
|
104
|
+
|
105
|
+
# hacky way to detect that command is a registered Thor::Group command
|
106
|
+
def thor_group_command?
|
107
|
+
command_params(raw=true) == [[:rest, :args]]
|
108
|
+
end
|
109
|
+
|
110
|
+
def found?(command)
|
111
|
+
public_methods = @command_class.public_instance_methods(false)
|
112
|
+
command && public_methods.include?(command.to_sym)
|
113
|
+
end
|
114
|
+
|
115
|
+
# all top-level commands
|
116
|
+
def all_commands
|
117
|
+
commands = @command_class.all_commands.reject do |k,v|
|
118
|
+
v.is_a?(Thor::HiddenCommand)
|
119
|
+
end
|
120
|
+
commands.keys
|
121
|
+
end
|
122
|
+
|
123
|
+
def command_params(raw=false)
|
124
|
+
params = @command_class.instance_method(@current_command).parameters
|
125
|
+
# Example:
|
126
|
+
# >> Sub.instance_method(:goodbye).parameters
|
127
|
+
# => [[:req, :name]]
|
128
|
+
# >>
|
129
|
+
raw ? params : params.map!(&:last)
|
130
|
+
end
|
131
|
+
|
132
|
+
def params_completion
|
133
|
+
offset = @params.size - 1
|
134
|
+
offset_params = command_params[offset..-1]
|
135
|
+
command_params[offset..-1].first
|
136
|
+
end
|
137
|
+
|
138
|
+
def options_completion
|
139
|
+
used = ARGV.select { |a| a.include?('--') } # so we can remove used options
|
140
|
+
|
141
|
+
method_options = @command_class.all_commands[@current_command].options.keys
|
142
|
+
class_options = @command_class.class_options.keys
|
143
|
+
|
144
|
+
all_options = method_options + class_options + ['help']
|
145
|
+
|
146
|
+
all_options.map! { |o| "--#{o.to_s.gsub('_','-')}" }
|
147
|
+
filtered_options = all_options - used
|
148
|
+
filtered_options.uniq
|
149
|
+
end
|
150
|
+
|
151
|
+
# Useful for debugging. Using puts messes up completion.
|
152
|
+
def log(msg)
|
153
|
+
File.open("/tmp/complete.log", "a") do |file|
|
154
|
+
file.puts(msg)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|