cli-kit 4.0.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|