cli-kit 4.0.0 → 5.0.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 +4 -4
- data/.github/workflows/cla.yml +22 -0
- data/.github/workflows/ruby.yml +34 -2
- data/.gitignore +2 -0
- data/.rubocop.sorbet.yml +47 -0
- data/.rubocop.yml +16 -1
- data/Gemfile +10 -1
- data/Gemfile.lock +94 -18
- data/README.md +46 -3
- data/Rakefile +1 -0
- data/bin/onchange +30 -0
- data/bin/tapioca +29 -0
- data/bin/testunit +1 -0
- data/cli-kit.gemspec +2 -2
- data/dev.yml +35 -3
- data/examples/minimal/example.rb +3 -1
- data/examples/single-file/example.rb +25 -35
- data/gen/lib/gen/commands/help.rb +8 -10
- data/gen/lib/gen/commands/new.rb +23 -9
- data/gen/lib/gen/commands.rb +21 -9
- data/gen/lib/gen/entry_point.rb +12 -3
- data/gen/lib/gen/generator.rb +28 -7
- data/gen/lib/gen/help.rb +63 -0
- data/gen/lib/gen.rb +18 -23
- data/gen/template/bin/update-deps +2 -2
- data/gen/template/lib/__app__/commands.rb +1 -4
- data/gen/template/lib/__app__.rb +8 -17
- data/gen/template/test/example_test.rb +1 -1
- data/lib/cli/kit/args/definition.rb +344 -0
- data/lib/cli/kit/args/evaluation.rb +245 -0
- data/lib/cli/kit/args/parser/node.rb +132 -0
- data/lib/cli/kit/args/parser.rb +129 -0
- data/lib/cli/kit/args/tokenizer.rb +133 -0
- data/lib/cli/kit/args.rb +16 -0
- data/lib/cli/kit/base_command.rb +17 -32
- data/lib/cli/kit/command_help.rb +271 -0
- data/lib/cli/kit/command_registry.rb +69 -17
- data/lib/cli/kit/config.rb +25 -22
- data/lib/cli/kit/core_ext.rb +30 -0
- data/lib/cli/kit/error_handler.rb +131 -70
- data/lib/cli/kit/executor.rb +19 -3
- data/lib/cli/kit/ini.rb +31 -38
- data/lib/cli/kit/levenshtein.rb +12 -4
- data/lib/cli/kit/logger.rb +16 -2
- data/lib/cli/kit/opts.rb +301 -0
- data/lib/cli/kit/resolver.rb +8 -0
- data/lib/cli/kit/sorbet_runtime_stub.rb +156 -0
- data/lib/cli/kit/support/test_helper.rb +23 -14
- data/lib/cli/kit/support.rb +2 -0
- data/lib/cli/kit/system.rb +188 -54
- data/lib/cli/kit/util.rb +48 -103
- data/lib/cli/kit/version.rb +3 -1
- data/lib/cli/kit.rb +103 -7
- metadata +22 -10
- data/.github/probots.yml +0 -2
- data/lib/cli/kit/autocall.rb +0 -21
- data/lib/cli/kit/ruby_backports/enumerable.rb +0 -6
@@ -1,19 +1,17 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'gen'
|
2
4
|
|
3
5
|
module Gen
|
4
6
|
module Commands
|
5
7
|
class Help < Gen::Command
|
6
|
-
|
7
|
-
|
8
|
-
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
desc('Show help for a command, or this page')
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
puts CLI::UI.fmt(help)
|
14
|
-
end
|
15
|
-
puts ''
|
16
|
-
end
|
12
|
+
sig { params(args: T::Array[String], _name: String).void }
|
13
|
+
def call(args, _name)
|
14
|
+
Gen::Help.generate(args)
|
17
15
|
end
|
18
16
|
end
|
19
17
|
end
|
data/gen/lib/gen/commands/new.rb
CHANGED
@@ -1,20 +1,34 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'gen'
|
2
4
|
|
3
5
|
module Gen
|
4
6
|
module Commands
|
5
7
|
class New < Gen::Command
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
command_name('new')
|
11
|
+
desc('Create a new project')
|
12
|
+
long_desc(<<~LONGDESC)
|
13
|
+
This is currently a very simple command. In theory it could be extended
|
14
|
+
to support a number of flags like {{command:bundle gem}}, etc. As it is, it only
|
15
|
+
takes an application name.
|
16
|
+
LONGDESC
|
17
|
+
usage('[app_name]')
|
18
|
+
example('mycliapp', "create a new project called 'mycliapp'")
|
12
19
|
|
13
|
-
|
20
|
+
class Opts < CLI::Kit::Opts
|
21
|
+
extend(T::Sig)
|
22
|
+
|
23
|
+
sig { returns(String) }
|
24
|
+
def project_name
|
25
|
+
position!
|
26
|
+
end
|
14
27
|
end
|
15
28
|
|
16
|
-
|
17
|
-
|
29
|
+
sig { params(op: Opts, _name: T.untyped).returns(T.untyped) }
|
30
|
+
def invoke(op, _name)
|
31
|
+
Gen::Generator.run(op.project_name)
|
18
32
|
end
|
19
33
|
end
|
20
34
|
end
|
data/gen/lib/gen/commands.rb
CHANGED
@@ -1,18 +1,30 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'gen'
|
2
4
|
|
3
5
|
module Gen
|
4
6
|
module Commands
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
)
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
Registry = CLI::Kit::CommandRegistry.new(default: 'help')
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
class << self
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig do
|
15
|
+
params(const: Symbol, cmd: String, path: String, lamda_const: T.proc.returns(T.class_of(Gen::Command))).void
|
16
|
+
end
|
17
|
+
def register(const, cmd, path, lamda_const)
|
18
|
+
autoload(const, path)
|
19
|
+
Registry.add(lamda_const, cmd)
|
20
|
+
end
|
13
21
|
end
|
14
22
|
|
15
|
-
register :Help, 'help', 'gen/commands/help'
|
16
|
-
register :New, 'new', 'gen/commands/new'
|
23
|
+
register :Help, 'help', 'gen/commands/help', -> { Commands::Help }
|
24
|
+
register :New, 'new', 'gen/commands/new', -> { Commands::New }
|
25
|
+
|
26
|
+
# TODO(burke): Really, cli-kit needs to handle global flags/options.
|
27
|
+
Registry.add_alias('-h', 'help')
|
28
|
+
Registry.add_alias('--help', 'help')
|
17
29
|
end
|
18
30
|
end
|
data/gen/lib/gen/entry_point.rb
CHANGED
@@ -1,10 +1,19 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'gen'
|
2
4
|
|
3
5
|
module Gen
|
4
6
|
module EntryPoint
|
5
|
-
|
6
|
-
|
7
|
-
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
class << self
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { params(args: T::Array[String]).void }
|
13
|
+
def call(args)
|
14
|
+
cmd, command_name, args = Gen::Resolver.call(args)
|
15
|
+
Gen::Executor.call(cmd, command_name, args)
|
16
|
+
end
|
8
17
|
end
|
9
18
|
end
|
10
19
|
end
|
data/gen/lib/gen/generator.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'gen'
|
2
4
|
require 'fileutils'
|
3
5
|
require 'open3'
|
@@ -6,8 +8,15 @@ require 'tmpdir'
|
|
6
8
|
|
7
9
|
module Gen
|
8
10
|
class Generator
|
9
|
-
|
10
|
-
|
11
|
+
extend T::Sig
|
12
|
+
|
13
|
+
class << self
|
14
|
+
extend T::Sig
|
15
|
+
|
16
|
+
sig { params(project_name: String).void }
|
17
|
+
def run(project_name)
|
18
|
+
new(project_name).run
|
19
|
+
end
|
11
20
|
end
|
12
21
|
|
13
22
|
TEMPLATE_ROOT = File.expand_path('gen/template', Gen::ROOT)
|
@@ -35,15 +44,17 @@ module Gen
|
|
35
44
|
}.freeze
|
36
45
|
private_constant :BUNDLER_TRANSLATIONS
|
37
46
|
|
47
|
+
sig { params(project_name: String).void }
|
38
48
|
def initialize(project_name)
|
39
49
|
raise(
|
40
50
|
CLI::Kit::Abort,
|
41
|
-
"project name must match {{bold:#{VALID_PROJECT_NAME}}} (but can be changed later)"
|
51
|
+
"project name must match {{bold:#{VALID_PROJECT_NAME}}} (but can be changed later)",
|
42
52
|
) unless project_name =~ VALID_PROJECT_NAME
|
43
53
|
@project_name = project_name
|
44
54
|
@title_case_project_name = @project_name.sub(/^./, &:upcase)
|
45
55
|
end
|
46
56
|
|
57
|
+
sig { void }
|
47
58
|
def run
|
48
59
|
vendor = ask_vendor?
|
49
60
|
create_project_dir
|
@@ -57,11 +68,12 @@ module Gen
|
|
57
68
|
|
58
69
|
private
|
59
70
|
|
71
|
+
sig { returns(T::Boolean) }
|
60
72
|
def ask_vendor?
|
61
73
|
return true if ENV['DEPS'] == 'vendor'
|
62
74
|
return false if ENV['DEPS'] == 'bundler'
|
63
75
|
|
64
|
-
vendor = nil
|
76
|
+
vendor = T.let(nil, T.nilable(String))
|
65
77
|
CLI::UI::Frame.open('Configuration') do
|
66
78
|
q = 'How would you like the application to consume {{command:cli-kit}} and {{command:cli-ui}}?'
|
67
79
|
vendor = CLI::UI::Prompt.ask(q) do |c|
|
@@ -72,6 +84,7 @@ module Gen
|
|
72
84
|
vendor == 'vendor'
|
73
85
|
end
|
74
86
|
|
87
|
+
sig { void }
|
75
88
|
def create_project_dir
|
76
89
|
info(create: '')
|
77
90
|
FileUtils.mkdir(@project_name)
|
@@ -79,10 +92,12 @@ module Gen
|
|
79
92
|
error("directory already exists: #{@project_name}")
|
80
93
|
end
|
81
94
|
|
95
|
+
sig { params(translations: T::Hash[String, T.any(FalseClass, String)]).void }
|
82
96
|
def copy_files(translations:)
|
83
97
|
each_template_file do |source_name|
|
84
98
|
target_name = translations.fetch(source_name, source_name)
|
85
99
|
next if target_name == false
|
100
|
+
|
86
101
|
target_name = apply_template_variables(target_name)
|
87
102
|
|
88
103
|
source = File.join(TEMPLATE_ROOT, source_name)
|
@@ -100,6 +115,7 @@ module Gen
|
|
100
115
|
end
|
101
116
|
end
|
102
117
|
|
118
|
+
sig { void }
|
103
119
|
def update_deps
|
104
120
|
Dir.mktmpdir do |tmp|
|
105
121
|
clone(tmp, 'cli-ui')
|
@@ -111,6 +127,7 @@ module Gen
|
|
111
127
|
end
|
112
128
|
end
|
113
129
|
|
130
|
+
sig { params(dir: String, repo: String).void }
|
114
131
|
def clone(dir, repo)
|
115
132
|
info(clone: repo)
|
116
133
|
out, stat = Open3.capture2e('git', '-C', dir, 'clone', "https://github.com/shopify/#{repo}")
|
@@ -120,9 +137,8 @@ module Gen
|
|
120
137
|
end
|
121
138
|
end
|
122
139
|
|
123
|
-
|
124
|
-
|
125
|
-
|
140
|
+
sig { params(block: T.proc.params(rel_path: String).void).void }
|
141
|
+
def each_template_file(&block)
|
126
142
|
root = Pathname.new(TEMPLATE_ROOT)
|
127
143
|
Dir.glob("#{TEMPLATE_ROOT}/**/*").each do |f|
|
128
144
|
el = Pathname.new(f)
|
@@ -130,6 +146,7 @@ module Gen
|
|
130
146
|
end
|
131
147
|
end
|
132
148
|
|
149
|
+
sig { params(s: String).returns(String) }
|
133
150
|
def apply_template_variables(s)
|
134
151
|
s
|
135
152
|
.gsub(/__app__/, @project_name)
|
@@ -138,16 +155,19 @@ module Gen
|
|
138
155
|
.gsub(/__cli-ui-version__/, cli_ui_version)
|
139
156
|
end
|
140
157
|
|
158
|
+
sig { returns(String) }
|
141
159
|
def cli_kit_version
|
142
160
|
require 'cli/kit/version'
|
143
161
|
CLI::Kit::VERSION.to_s
|
144
162
|
end
|
145
163
|
|
164
|
+
sig { returns(String) }
|
146
165
|
def cli_ui_version
|
147
166
|
require 'cli/ui/version'
|
148
167
|
CLI::UI::VERSION.to_s
|
149
168
|
end
|
150
169
|
|
170
|
+
sig { params(create: T.nilable(String), clone: T.nilable(String), run: T.nilable(String)).void }
|
151
171
|
def info(create: nil, clone: nil, run: nil)
|
152
172
|
if clone
|
153
173
|
puts(CLI::UI.fmt("\t{{bold:{{yellow:clone}}\t#{clone}}}"))
|
@@ -158,6 +178,7 @@ module Gen
|
|
158
178
|
end
|
159
179
|
end
|
160
180
|
|
181
|
+
sig { params(msg: String).void }
|
161
182
|
def error(msg)
|
162
183
|
raise(CLI::Kit::Abort, msg)
|
163
184
|
end
|
data/gen/lib/gen/help.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
3
|
+
require 'gen'
|
4
|
+
|
5
|
+
module Gen
|
6
|
+
module Help
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
class << self
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
sig { params(path: T::Array[String], to: IO).void }
|
13
|
+
def generate(path, to: STDOUT)
|
14
|
+
case path.size
|
15
|
+
when 0
|
16
|
+
generate_toplevel(to: to)
|
17
|
+
when 1
|
18
|
+
generate_command_help(T.must(path.first), to: to)
|
19
|
+
else
|
20
|
+
raise(NotImplementedError, 'subcommand help not implemented')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { params(to: IO).void }
|
25
|
+
def generate_toplevel(to: STDOUT)
|
26
|
+
to.write(CLI::UI.fmt(<<~HELP))
|
27
|
+
{{bold:{{command:cli-kit}} generates new cli-kit apps.}}
|
28
|
+
|
29
|
+
It basically only has one command: {{command:cli-kit new}}.
|
30
|
+
|
31
|
+
See {{command:cli-kit new --help}} for more information.
|
32
|
+
|
33
|
+
{{bold:Available commands:}}
|
34
|
+
HELP
|
35
|
+
|
36
|
+
cmds = Gen::Commands::Registry.resolved_commands.map do |name, klass|
|
37
|
+
[name, klass._desc]
|
38
|
+
end
|
39
|
+
|
40
|
+
max_len = cmds.map(&:first).map(&:length).max
|
41
|
+
|
42
|
+
cmds.each do |name, desc|
|
43
|
+
to.write(CLI::UI.fmt(" {{command:#{name.ljust(max_len)}}} #{desc}\n"))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
sig { params(cmd_name: String, to: IO).void }
|
48
|
+
def generate_command_help(cmd_name, to: STDOUT)
|
49
|
+
klass = Gen::Commands::Registry.resolved_commands[cmd_name]
|
50
|
+
unless klass
|
51
|
+
to.write(CLI::UI.fmt(<<~HELP))
|
52
|
+
{{red:{{bold:No help found for: #{cmd_name}}}}}
|
53
|
+
|
54
|
+
HELP
|
55
|
+
generate_toplevel(to: to)
|
56
|
+
raise(CLI::Kit::AbortSilent)
|
57
|
+
end
|
58
|
+
|
59
|
+
klass.new.call(['--help'], cmd_name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/gen/lib/gen.rb
CHANGED
@@ -1,39 +1,34 @@
|
|
1
|
+
# typed: true
|
2
|
+
|
1
3
|
require 'cli/ui'
|
2
4
|
require 'cli/kit'
|
3
5
|
|
4
6
|
CLI::UI::StdoutRouter.enable
|
5
7
|
|
6
8
|
module Gen
|
7
|
-
extend CLI::Kit::Autocall
|
8
|
-
|
9
9
|
TOOL_NAME = 'cli-kit'
|
10
|
-
|
10
|
+
CLI::Kit::CommandHelp.tool_name = TOOL_NAME
|
11
|
+
|
12
|
+
ROOT = File.expand_path('../../..', __FILE__)
|
11
13
|
|
12
14
|
TOOL_CONFIG_PATH = File.expand_path(File.join('~', '.config', TOOL_NAME))
|
13
15
|
LOG_FILE = File.join(TOOL_CONFIG_PATH, 'logs', 'log.log')
|
14
16
|
DEBUG_LOG_FILE = File.join(TOOL_CONFIG_PATH, 'logs', 'debug.log')
|
15
17
|
|
16
|
-
autoload(:Generator,
|
17
|
-
|
18
|
+
autoload(:Generator, 'gen/generator')
|
18
19
|
autoload(:EntryPoint, 'gen/entry_point')
|
20
|
+
autoload(:Help, 'gen/help')
|
19
21
|
autoload(:Commands, 'gen/commands')
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
autocall(:ErrorHandler) do
|
34
|
-
CLI::Kit::ErrorHandler.new(
|
35
|
-
log_file: LOG_FILE,
|
36
|
-
exception_reporter: nil
|
37
|
-
)
|
38
|
-
end
|
23
|
+
Config = CLI::Kit::Config.new(tool_name: TOOL_NAME)
|
24
|
+
Command = CLI::Kit::BaseCommand
|
25
|
+
Logger = CLI::Kit::Logger.new(debug_log_file: DEBUG_LOG_FILE)
|
26
|
+
|
27
|
+
Executor = CLI::Kit::Executor.new(log_file: LOG_FILE)
|
28
|
+
Resolver = CLI::Kit::Resolver.new(
|
29
|
+
tool_name: TOOL_NAME,
|
30
|
+
command_registry: Gen::Commands::Registry,
|
31
|
+
)
|
32
|
+
|
33
|
+
ErrorHandler = CLI::Kit::ErrorHandler.new(log_file: LOG_FILE)
|
39
34
|
end
|
@@ -49,7 +49,7 @@ deps.each do |dep|
|
|
49
49
|
dirty = false
|
50
50
|
|
51
51
|
Dir.chdir(path) do
|
52
|
-
_, _, stat = Open3.capture3('git fetch origin
|
52
|
+
_, _, stat = Open3.capture3('git fetch origin main')
|
53
53
|
bail("couldn't git fetch in dependency: {{yellow:#{dep}}}") unless stat.success?
|
54
54
|
|
55
55
|
head_sha, stat = Open3.capture2('git rev-parse HEAD')
|
@@ -68,7 +68,7 @@ deps.each do |dep|
|
|
68
68
|
"Copying files from {{yellow:#{path}}} to satisfy dependency {{yellow:#{dep}}}.\n" \
|
69
69
|
" However, the repo at {{yellow:#{path}}} isn't up to date.\n" \
|
70
70
|
" The checked-out revision is {{yellow:#{head_sha[0..8]}}}, and "\
|
71
|
-
"{{yellow:origin/
|
71
|
+
"{{yellow:origin/main}} is {{yellow:#{fetch_head_sha[0..8]}}}.\n" \
|
72
72
|
" Unless you know what you're doing, you should {{green:cd}} to that repo and {{green:git pull}}, then run this again."
|
73
73
|
)
|
74
74
|
end
|
@@ -2,10 +2,7 @@ require '__app__'
|
|
2
2
|
|
3
3
|
module __App__
|
4
4
|
module Commands
|
5
|
-
Registry = CLI::Kit::CommandRegistry.new(
|
6
|
-
default: 'help',
|
7
|
-
contextual_resolver: nil
|
8
|
-
)
|
5
|
+
Registry = CLI::Kit::CommandRegistry.new(default: 'help')
|
9
6
|
|
10
7
|
def self.register(const, cmd, path)
|
11
8
|
autoload(const, path)
|
data/gen/template/lib/__app__.rb
CHANGED
@@ -4,8 +4,6 @@ require 'cli/kit'
|
|
4
4
|
CLI::UI::StdoutRouter.enable
|
5
5
|
|
6
6
|
module __App__
|
7
|
-
extend CLI::Kit::Autocall
|
8
|
-
|
9
7
|
TOOL_NAME = '__app__'
|
10
8
|
ROOT = File.expand_path('../..', __FILE__)
|
11
9
|
LOG_FILE = '/tmp/__app__.log'
|
@@ -13,21 +11,14 @@ module __App__
|
|
13
11
|
autoload(:EntryPoint, '__app__/entry_point')
|
14
12
|
autoload(:Commands, '__app__/commands')
|
15
13
|
|
16
|
-
|
17
|
-
|
14
|
+
Config = CLI::Kit::Config.new(tool_name: TOOL_NAME)
|
15
|
+
Command = CLI::Kit::BaseCommand
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
)
|
25
|
-
end
|
17
|
+
Executor = CLI::Kit::Executor.new(log_file: LOG_FILE)
|
18
|
+
Resolver = CLI::Kit::Resolver.new(
|
19
|
+
tool_name: TOOL_NAME,
|
20
|
+
command_registry: __App__::Commands::Registry
|
21
|
+
)
|
26
22
|
|
27
|
-
|
28
|
-
CLI::Kit::ErrorHandler.new(
|
29
|
-
log_file: LOG_FILE,
|
30
|
-
exception_reporter: nil
|
31
|
-
)
|
32
|
-
end
|
23
|
+
ErrorHandler = CLI::Kit::ErrorHandler.new(log_file: LOG_FILE)
|
33
24
|
end
|