clamp 1.4.0 → 1.5.1
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 +4 -4
- data/CHANGES.md +8 -0
- data/README.md +44 -0
- data/examples/gitdown +1 -0
- data/lib/clamp/completion/bash_generator.rb +207 -0
- data/lib/clamp/completion/fish_generator.rb +176 -0
- data/lib/clamp/completion/zsh_generator.rb +123 -0
- data/lib/clamp/completion.rb +187 -0
- data/lib/clamp/version.rb +1 -1
- metadata +5 -21
- data/.autotest +0 -11
- data/.editorconfig +0 -10
- data/.github/workflows/ci.yml +0 -31
- data/.gitignore +0 -9
- data/.rspec +0 -2
- data/.rubocop.yml +0 -74
- data/CODEOWNERS +0 -1
- data/Gemfile +0 -20
- data/Guardfile +0 -45
- data/Rakefile +0 -18
- data/clamp.gemspec +0 -28
- data/spec/clamp/command_group_spec.rb +0 -438
- data/spec/clamp/command_option_module_spec.rb +0 -40
- data/spec/clamp/command_option_reordering_spec.rb +0 -58
- data/spec/clamp/command_spec.rb +0 -1280
- data/spec/clamp/help/builder_spec.rb +0 -81
- data/spec/clamp/messages_spec.rb +0 -50
- data/spec/clamp/option/definition_spec.rb +0 -343
- data/spec/clamp/parameter/definition_spec.rb +0 -314
- data/spec/spec_helper.rb +0 -65
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "clamp/command"
|
|
4
|
+
require "clamp/completion/bash_generator"
|
|
5
|
+
require "clamp/completion/fish_generator"
|
|
6
|
+
require "clamp/completion/zsh_generator"
|
|
7
|
+
|
|
8
|
+
module Clamp
|
|
9
|
+
|
|
10
|
+
# Shell completion script generation.
|
|
11
|
+
#
|
|
12
|
+
module Completion
|
|
13
|
+
|
|
14
|
+
GENERATORS = {
|
|
15
|
+
bash: Clamp::Completion::BashGenerator,
|
|
16
|
+
fish: Clamp::Completion::FishGenerator,
|
|
17
|
+
zsh: Clamp::Completion::ZshGenerator
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# Raised when --shell-completions is used; caught by Command.run.
|
|
21
|
+
#
|
|
22
|
+
class Wanted < StandardError
|
|
23
|
+
|
|
24
|
+
def initialize(command, shell)
|
|
25
|
+
super("completion requested")
|
|
26
|
+
@command = command
|
|
27
|
+
@shell = shell
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
attr_reader :command, :shell
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
module_function
|
|
35
|
+
|
|
36
|
+
# Encode a name for use as a shell function identifier.
|
|
37
|
+
# Special characters are replaced with _XX hex codes.
|
|
38
|
+
def encode_name(name)
|
|
39
|
+
name.gsub(/[^a-zA-Z0-9_]/) { |c| format("_%02x", c.ord) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def generate(command_class, shell, executable_name)
|
|
43
|
+
generator_class = GENERATORS.fetch(shell) do
|
|
44
|
+
raise ArgumentError, "unsupported shell: #{shell.inspect}"
|
|
45
|
+
end
|
|
46
|
+
generator_class.new(command_class, executable_name).generate
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Return switches with --[no-]foo expanded to --foo and --no-foo.
|
|
50
|
+
def expanded_switches(option)
|
|
51
|
+
option.switches.flat_map do |switch|
|
|
52
|
+
if switch =~ /^--\[no-\](.*)/
|
|
53
|
+
["--#{Regexp.last_match(1)}", "--no-#{Regexp.last_match(1)}"]
|
|
54
|
+
else
|
|
55
|
+
switch
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Options visible in completion (excludes hidden).
|
|
61
|
+
def visible_options(command_class)
|
|
62
|
+
command_class.recognised_options.reject(&:hidden?)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Walk the command tree depth-first, yielding (command_class, path, has_children).
|
|
66
|
+
# Path is an array of Subcommand::Definition objects.
|
|
67
|
+
# Always yields, even for revisited classes (with has_children=false).
|
|
68
|
+
def walk_command_tree(command_class, path = [], visited = Set.new, &block)
|
|
69
|
+
fresh = !visited.include?(command_class)
|
|
70
|
+
visited |= [command_class]
|
|
71
|
+
has_children = command_class.has_subcommands? && fresh
|
|
72
|
+
yield command_class, path, has_children
|
|
73
|
+
return unless has_children
|
|
74
|
+
|
|
75
|
+
command_class.recognised_subcommands.each do |sub|
|
|
76
|
+
walk_command_tree(sub.subcommand_class, path + [sub], visited, &block)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Count required, non-multivalued parameters for a command.
|
|
81
|
+
def required_parameter_count(command_class)
|
|
82
|
+
command_class.parameters.count { |p| p.required? && !p.multivalued? }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Return fish argparse optspecs for an option.
|
|
86
|
+
def argparse_specs_for(option)
|
|
87
|
+
switches = expanded_switches(option)
|
|
88
|
+
suffix = option.flag? ? "" : "="
|
|
89
|
+
short = switches.find { |s| s.match?(/^-[^-]$/) }
|
|
90
|
+
longs = switches.select { |s| s.start_with?("--") }
|
|
91
|
+
if short && longs.length == 1
|
|
92
|
+
["#{short.delete_prefix('-')}/#{longs.first.delete_prefix('--')}#{suffix}"]
|
|
93
|
+
else
|
|
94
|
+
longs.map { |l| "#{l.delete_prefix('--')}#{suffix}" }
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Collect all subcommand names across the command tree.
|
|
99
|
+
def collect_subcommand_names(command_class)
|
|
100
|
+
names = []
|
|
101
|
+
walk_command_tree(command_class) do |cmd, _path, has_children|
|
|
102
|
+
cmd.recognised_subcommands.each { |sub| names.concat(sub.names) } if has_children
|
|
103
|
+
end
|
|
104
|
+
names.uniq
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
module Clamp
|
|
111
|
+
|
|
112
|
+
# Reopened to add completion support.
|
|
113
|
+
#
|
|
114
|
+
class Command
|
|
115
|
+
|
|
116
|
+
def self.generate_completion(shell, executable_name)
|
|
117
|
+
Clamp::Completion.generate(self, shell, executable_name)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Adds --shell-completions option and handles the Wanted exception.
|
|
121
|
+
#
|
|
122
|
+
module RunWithCompletion
|
|
123
|
+
|
|
124
|
+
def run(invocation_path = File.basename($PROGRAM_NAME), arguments = ARGV, context = {})
|
|
125
|
+
context[:root_command_class] ||= self
|
|
126
|
+
super
|
|
127
|
+
rescue Clamp::Completion::Wanted => e
|
|
128
|
+
shell_name = File.basename(e.shell).to_sym
|
|
129
|
+
begin
|
|
130
|
+
puts generate_completion(shell_name, invocation_path)
|
|
131
|
+
rescue ArgumentError => ex
|
|
132
|
+
$stderr.puts "ERROR: #{ex.message}"
|
|
133
|
+
exit(1)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class << self
|
|
140
|
+
|
|
141
|
+
prepend RunWithCompletion
|
|
142
|
+
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
module Clamp
|
|
150
|
+
module Option
|
|
151
|
+
|
|
152
|
+
# Adds implicit --shell-completions option to all commands.
|
|
153
|
+
#
|
|
154
|
+
module Declaration
|
|
155
|
+
|
|
156
|
+
# Declares --shell-completions alongside other implicit options.
|
|
157
|
+
#
|
|
158
|
+
module WithCompletionOption
|
|
159
|
+
|
|
160
|
+
def recognised_options
|
|
161
|
+
unless @implicit_completion_option_declared
|
|
162
|
+
@implicit_completion_option_declared = true
|
|
163
|
+
declare_implicit_completion_option
|
|
164
|
+
end
|
|
165
|
+
super
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def declare_implicit_completion_option
|
|
171
|
+
return if effective_options.find { |o| o.handles?("--shell-completions") }
|
|
172
|
+
|
|
173
|
+
option "--shell-completions", "SHELL",
|
|
174
|
+
"generate shell completion script",
|
|
175
|
+
hidden: true do |shell|
|
|
176
|
+
raise Clamp::Completion::Wanted.new(self, shell)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
prepend WithCompletionOption
|
|
183
|
+
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
end
|
|
187
|
+
end
|
data/lib/clamp/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clamp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Williams
|
|
@@ -17,20 +17,9 @@ executables: []
|
|
|
17
17
|
extensions: []
|
|
18
18
|
extra_rdoc_files: []
|
|
19
19
|
files:
|
|
20
|
-
- ".autotest"
|
|
21
|
-
- ".editorconfig"
|
|
22
|
-
- ".github/workflows/ci.yml"
|
|
23
|
-
- ".gitignore"
|
|
24
|
-
- ".rspec"
|
|
25
|
-
- ".rubocop.yml"
|
|
26
20
|
- CHANGES.md
|
|
27
|
-
- CODEOWNERS
|
|
28
|
-
- Gemfile
|
|
29
|
-
- Guardfile
|
|
30
21
|
- LICENSE
|
|
31
22
|
- README.md
|
|
32
|
-
- Rakefile
|
|
33
|
-
- clamp.gemspec
|
|
34
23
|
- examples/admin
|
|
35
24
|
- examples/defaulted
|
|
36
25
|
- examples/flipflop
|
|
@@ -45,6 +34,10 @@ files:
|
|
|
45
34
|
- lib/clamp/attribute/definition.rb
|
|
46
35
|
- lib/clamp/attribute/instance.rb
|
|
47
36
|
- lib/clamp/command.rb
|
|
37
|
+
- lib/clamp/completion.rb
|
|
38
|
+
- lib/clamp/completion/bash_generator.rb
|
|
39
|
+
- lib/clamp/completion/fish_generator.rb
|
|
40
|
+
- lib/clamp/completion/zsh_generator.rb
|
|
48
41
|
- lib/clamp/errors.rb
|
|
49
42
|
- lib/clamp/help.rb
|
|
50
43
|
- lib/clamp/messages.rb
|
|
@@ -60,15 +53,6 @@ files:
|
|
|
60
53
|
- lib/clamp/subcommand/parsing.rb
|
|
61
54
|
- lib/clamp/truthy.rb
|
|
62
55
|
- lib/clamp/version.rb
|
|
63
|
-
- spec/clamp/command_group_spec.rb
|
|
64
|
-
- spec/clamp/command_option_module_spec.rb
|
|
65
|
-
- spec/clamp/command_option_reordering_spec.rb
|
|
66
|
-
- spec/clamp/command_spec.rb
|
|
67
|
-
- spec/clamp/help/builder_spec.rb
|
|
68
|
-
- spec/clamp/messages_spec.rb
|
|
69
|
-
- spec/clamp/option/definition_spec.rb
|
|
70
|
-
- spec/clamp/parameter/definition_spec.rb
|
|
71
|
-
- spec/spec_helper.rb
|
|
72
56
|
homepage: https://github.com/mdub/clamp
|
|
73
57
|
licenses:
|
|
74
58
|
- MIT
|
data/.autotest
DELETED
data/.editorconfig
DELETED
data/.github/workflows/ci.yml
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [master]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
test:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
strategy:
|
|
14
|
-
fail-fast: false
|
|
15
|
-
matrix:
|
|
16
|
-
ruby-version:
|
|
17
|
-
- "3.1"
|
|
18
|
-
- "3.4"
|
|
19
|
-
- "4.0"
|
|
20
|
-
|
|
21
|
-
steps:
|
|
22
|
-
- uses: actions/checkout@v4
|
|
23
|
-
|
|
24
|
-
- name: Set up Ruby ${{ matrix.ruby-version }}
|
|
25
|
-
uses: ruby/setup-ruby@v1
|
|
26
|
-
with:
|
|
27
|
-
ruby-version: ${{ matrix.ruby-version }}
|
|
28
|
-
bundler-cache: true
|
|
29
|
-
|
|
30
|
-
- name: Run tests and linter
|
|
31
|
-
run: bundle exec rake
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.rubocop.yml
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
plugins:
|
|
2
|
-
- rubocop-rake
|
|
3
|
-
- rubocop-rspec
|
|
4
|
-
|
|
5
|
-
AllCops:
|
|
6
|
-
TargetRubyVersion: 2.5
|
|
7
|
-
NewCops: enable
|
|
8
|
-
|
|
9
|
-
Layout/LineLength:
|
|
10
|
-
Max: 120
|
|
11
|
-
|
|
12
|
-
Layout/EmptyLinesAroundBlockBody:
|
|
13
|
-
Enabled: false
|
|
14
|
-
|
|
15
|
-
Layout/EmptyLinesAroundClassBody:
|
|
16
|
-
EnforcedStyle: empty_lines
|
|
17
|
-
|
|
18
|
-
Layout/EmptyLinesAroundModuleBody:
|
|
19
|
-
Enabled: false
|
|
20
|
-
|
|
21
|
-
Metrics/AbcSize:
|
|
22
|
-
Enabled: false
|
|
23
|
-
|
|
24
|
-
Metrics/BlockLength:
|
|
25
|
-
Exclude:
|
|
26
|
-
- "spec/**/*"
|
|
27
|
-
|
|
28
|
-
Metrics/MethodLength:
|
|
29
|
-
Max: 30
|
|
30
|
-
|
|
31
|
-
Naming/AccessorMethodName:
|
|
32
|
-
Enabled: false
|
|
33
|
-
|
|
34
|
-
Naming/FileName:
|
|
35
|
-
Exclude:
|
|
36
|
-
- "bin/*"
|
|
37
|
-
|
|
38
|
-
Naming/PredicatePrefix:
|
|
39
|
-
Enabled: false
|
|
40
|
-
|
|
41
|
-
Style/ClassAndModuleChildren:
|
|
42
|
-
EnforcedStyle: nested
|
|
43
|
-
Exclude:
|
|
44
|
-
- "spec/**/*"
|
|
45
|
-
|
|
46
|
-
Style/Documentation:
|
|
47
|
-
Exclude:
|
|
48
|
-
- "lib/**/version.rb"
|
|
49
|
-
- "examples/*"
|
|
50
|
-
- "spec/**/*"
|
|
51
|
-
|
|
52
|
-
Style/Encoding:
|
|
53
|
-
Enabled: true
|
|
54
|
-
|
|
55
|
-
Style/Lambda:
|
|
56
|
-
Enabled: false
|
|
57
|
-
|
|
58
|
-
Style/NumericLiterals:
|
|
59
|
-
Enabled: false
|
|
60
|
-
|
|
61
|
-
Style/StderrPuts:
|
|
62
|
-
Enabled: false
|
|
63
|
-
|
|
64
|
-
Style/StringLiterals:
|
|
65
|
-
EnforcedStyle: double_quotes
|
|
66
|
-
|
|
67
|
-
Style/WordArray:
|
|
68
|
-
Enabled: false
|
|
69
|
-
|
|
70
|
-
RSpec/NestedGroups:
|
|
71
|
-
Enabled: false
|
|
72
|
-
|
|
73
|
-
RSpec/Output:
|
|
74
|
-
Enabled: false
|
data/CODEOWNERS
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
* @mdub
|
data/Gemfile
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
source "https://rubygems.org"
|
|
4
|
-
|
|
5
|
-
gemspec
|
|
6
|
-
|
|
7
|
-
group :development do
|
|
8
|
-
gem "guard-rspec", "~> 4.7", require: false
|
|
9
|
-
gem "highline"
|
|
10
|
-
gem "listen", "~> 3.9"
|
|
11
|
-
gem "pry-byebug", "~> 3.11"
|
|
12
|
-
gem "rake", "~> 13.3"
|
|
13
|
-
gem "rubocop", "~> 1.84.1", require: false
|
|
14
|
-
gem "rubocop-rake", "~> 0.7.1", require: false
|
|
15
|
-
gem "rubocop-rspec", "~> 3.9.0", require: false
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
group :test do
|
|
19
|
-
gem "rspec", "~> 3.13"
|
|
20
|
-
end
|
data/Guardfile
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# A sample Guardfile
|
|
4
|
-
# More info at https://github.com/guard/guard#readme
|
|
5
|
-
|
|
6
|
-
## Uncomment and set this to only include directories you want to watch
|
|
7
|
-
# directories %w(app lib config test spec features) \
|
|
8
|
-
# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
|
9
|
-
|
|
10
|
-
## NOTE: if you are using the `directories` clause above and you are not
|
|
11
|
-
## watching the project directory ('.'), then you will want to move
|
|
12
|
-
## the Guardfile to a watched dir and symlink it back, e.g.
|
|
13
|
-
#
|
|
14
|
-
# $ mkdir config
|
|
15
|
-
# $ mv Guardfile config/
|
|
16
|
-
# $ ln -s config/Guardfile .
|
|
17
|
-
#
|
|
18
|
-
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
|
19
|
-
|
|
20
|
-
# NOTE: The cmd option is now required due to the increasing number of ways
|
|
21
|
-
# rspec may be run, below are examples of the most common uses.
|
|
22
|
-
# * bundler: 'bundle exec rspec'
|
|
23
|
-
# * bundler binstubs: 'bin/rspec'
|
|
24
|
-
# * spring: 'bin/rspec' (This will use spring if running and you have
|
|
25
|
-
# installed the spring binstubs per the docs)
|
|
26
|
-
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
|
27
|
-
# * 'just' rspec: 'rspec'
|
|
28
|
-
|
|
29
|
-
guard :rspec, cmd: "bundle exec rspec" do
|
|
30
|
-
require "guard/rspec/dsl"
|
|
31
|
-
dsl = Guard::RSpec::Dsl.new(self)
|
|
32
|
-
|
|
33
|
-
# Feel free to open issues for suggestions and improvements
|
|
34
|
-
|
|
35
|
-
# RSpec files
|
|
36
|
-
rspec = dsl.rspec
|
|
37
|
-
watch(rspec.spec_helper) { rspec.spec_dir }
|
|
38
|
-
watch(rspec.spec_support) { rspec.spec_dir }
|
|
39
|
-
watch(rspec.spec_files)
|
|
40
|
-
|
|
41
|
-
# Ruby files
|
|
42
|
-
ruby = dsl.ruby
|
|
43
|
-
dsl.watch_spec_files_for(ruby.lib_files)
|
|
44
|
-
|
|
45
|
-
end
|
data/Rakefile
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "bundler"
|
|
4
|
-
|
|
5
|
-
Bundler::GemHelper.install_tasks
|
|
6
|
-
|
|
7
|
-
require "rspec/core/rake_task"
|
|
8
|
-
|
|
9
|
-
RSpec::Core::RakeTask.new do |t|
|
|
10
|
-
t.pattern = "spec/**/*_spec.rb"
|
|
11
|
-
t.rspec_opts = ["--colour", "--format", "documentation"]
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
require "rubocop/rake_task"
|
|
15
|
-
|
|
16
|
-
RuboCop::RakeTask.new
|
|
17
|
-
|
|
18
|
-
task "default" => ["spec", "rubocop"]
|
data/clamp.gemspec
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
$LOAD_PATH.push File.expand_path("lib", __dir__)
|
|
4
|
-
require "clamp/version"
|
|
5
|
-
|
|
6
|
-
Gem::Specification.new do |s|
|
|
7
|
-
|
|
8
|
-
s.name = "clamp"
|
|
9
|
-
s.version = Clamp::VERSION.dup
|
|
10
|
-
s.platform = Gem::Platform::RUBY
|
|
11
|
-
s.authors = ["Mike Williams"]
|
|
12
|
-
s.email = "mdub@dogbiscuit.org"
|
|
13
|
-
s.homepage = "https://github.com/mdub/clamp"
|
|
14
|
-
|
|
15
|
-
s.license = "MIT"
|
|
16
|
-
|
|
17
|
-
s.summary = "a minimal framework for command-line utilities"
|
|
18
|
-
s.description = <<-TEXT.gsub(/^\s+/, "")
|
|
19
|
-
Clamp provides an object-model for command-line utilities.
|
|
20
|
-
It handles parsing of command-line options, and generation of usage help.
|
|
21
|
-
TEXT
|
|
22
|
-
|
|
23
|
-
s.files = `git ls-files`.split("\n")
|
|
24
|
-
s.require_paths = ["lib"]
|
|
25
|
-
|
|
26
|
-
s.required_ruby_version = ">= 2.5", "< 5"
|
|
27
|
-
s.metadata["rubygems_mfa_required"] = "true"
|
|
28
|
-
end
|