smol 1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +697 -0
- data/Rakefile +12 -0
- data/lib/smol/app.rb +122 -0
- data/lib/smol/app_lookup.rb +13 -0
- data/lib/smol/check.rb +70 -0
- data/lib/smol/check_result.rb +18 -0
- data/lib/smol/cli.rb +119 -0
- data/lib/smol/coercion.rb +18 -0
- data/lib/smol/colors.rb +27 -0
- data/lib/smol/command.rb +292 -0
- data/lib/smol/config.rb +48 -0
- data/lib/smol/config_display.rb +31 -0
- data/lib/smol/input.rb +60 -0
- data/lib/smol/output.rb +105 -0
- data/lib/smol/repl.rb +207 -0
- data/lib/smol/version.rb +3 -0
- data/lib/smol.rb +55 -0
- metadata +92 -0
data/lib/smol/config.rb
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smol
|
|
4
|
+
class Config
|
|
5
|
+
include Coercion
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@settings = {}
|
|
9
|
+
@values = {}
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def setting(key, default:, type: :string, desc: nil)
|
|
13
|
+
@settings[key] = { default: default, type: type, desc: desc }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def [](key)
|
|
17
|
+
return @values[key] if @values.key?(key)
|
|
18
|
+
|
|
19
|
+
setting = @settings[key]
|
|
20
|
+
raise ArgumentError, "unknown config key: #{key}" unless setting
|
|
21
|
+
|
|
22
|
+
env_key = key.to_s.upcase
|
|
23
|
+
raw = ENV.fetch(env_key, setting[:default].to_s)
|
|
24
|
+
|
|
25
|
+
@values[key] = coerce_value(raw, setting[:type])
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def set(key, value)
|
|
29
|
+
raise ArgumentError, "unknown config key: #{key}" unless @settings.key?(key)
|
|
30
|
+
|
|
31
|
+
@values[key] = coerce_value(value.to_s, @settings[key][:type])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def settings
|
|
35
|
+
@settings.dup
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_h
|
|
39
|
+
@settings.keys.each_with_object({}) { |k, h| h[k] = self[k] }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def each(&block)
|
|
43
|
+
return enum_for(:each) unless block
|
|
44
|
+
|
|
45
|
+
@settings.each_key { |k| yield k, self[k], @settings[k] }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smol
|
|
4
|
+
module ConfigDisplay
|
|
5
|
+
include Output
|
|
6
|
+
using Colors
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def show_config
|
|
11
|
+
out.puts "config:".bold
|
|
12
|
+
@app.config.each do |key, value, setting|
|
|
13
|
+
line = " #{key}: #{value}"
|
|
14
|
+
line += " - #{setting[:desc]}" if setting[:desc]
|
|
15
|
+
out.puts line.dim
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def set_config(key, value)
|
|
20
|
+
if key.nil? || value.nil?
|
|
21
|
+
warning "usage: config:set <key> <value>"
|
|
22
|
+
return
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@app.config.set(key.to_sym, value)
|
|
26
|
+
success "#{key} = #{@app.config[key.to_sym]}"
|
|
27
|
+
rescue ArgumentError => e
|
|
28
|
+
failure e.message
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/smol/input.rb
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smol
|
|
4
|
+
module Input
|
|
5
|
+
using Colors
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
def confirm(question, default: nil)
|
|
9
|
+
hint = case default
|
|
10
|
+
when true then "[Y/n]"
|
|
11
|
+
when false then "[y/N]"
|
|
12
|
+
else "[y/n]"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Smol.output.print "#{question} #{hint} ".yellow
|
|
16
|
+
response = Smol.input.gets&.strip&.downcase
|
|
17
|
+
|
|
18
|
+
case response
|
|
19
|
+
when "", nil
|
|
20
|
+
default
|
|
21
|
+
when "y", "yes"
|
|
22
|
+
true
|
|
23
|
+
when "n", "no"
|
|
24
|
+
false
|
|
25
|
+
else
|
|
26
|
+
default
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def ask(question, default: nil)
|
|
31
|
+
prompt = default ? "#{question} [#{default}]" : question
|
|
32
|
+
Smol.output.print "#{prompt}: ".yellow
|
|
33
|
+
response = Smol.input.gets&.strip
|
|
34
|
+
|
|
35
|
+
if response.nil? || response.empty?
|
|
36
|
+
default
|
|
37
|
+
else
|
|
38
|
+
response
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def choose(question, choices, default: nil)
|
|
43
|
+
Smol.output.puts question.yellow
|
|
44
|
+
choices.each_with_index do |choice, i|
|
|
45
|
+
marker = (i + 1) == default ? "*" : " "
|
|
46
|
+
Smol.output.puts "#{marker} #{i + 1}) #{choice}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Smol.output.print "choice: ".yellow
|
|
50
|
+
response = Smol.input.gets&.strip
|
|
51
|
+
|
|
52
|
+
if response.nil? || response.empty?
|
|
53
|
+
default ? choices[default - 1] : nil
|
|
54
|
+
else
|
|
55
|
+
idx = response.to_i - 1
|
|
56
|
+
idx >= 0 && idx < choices.size ? choices[idx] : nil
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/smol/output.rb
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smol
|
|
4
|
+
module Output
|
|
5
|
+
using Colors
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
def out
|
|
9
|
+
Smol.output
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def banner(text)
|
|
13
|
+
out.puts text.red
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def header(text)
|
|
17
|
+
out.puts text.bold
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def desc(text)
|
|
21
|
+
out.puts text.dim
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def nl
|
|
25
|
+
out.puts
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def info(text)
|
|
29
|
+
out.puts text
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def success(text)
|
|
33
|
+
out.puts text.green.bold
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def failure(text)
|
|
37
|
+
out.puts text.red.bold
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def warning(text)
|
|
41
|
+
out.puts text.yellow
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def hint(text)
|
|
45
|
+
out.puts text.dim
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def label(text)
|
|
49
|
+
out.puts text.yellow
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def verbose(text)
|
|
53
|
+
return unless Smol.verbose?
|
|
54
|
+
|
|
55
|
+
out.puts text.dim
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def debug(text)
|
|
59
|
+
return unless Smol.debug?
|
|
60
|
+
|
|
61
|
+
out.puts "[debug] #{text}".dim
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def check_result(name, result)
|
|
65
|
+
status = result.passed? ? "pass".green.bold : "fail".red.bold
|
|
66
|
+
out.puts "#{status}: #{name}"
|
|
67
|
+
out.puts " #{result.message}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def table(rows, headers: nil, indent: 0)
|
|
71
|
+
return if rows.empty?
|
|
72
|
+
|
|
73
|
+
all_rows = headers ? [headers] + rows : rows
|
|
74
|
+
col_widths = table_column_widths(all_rows)
|
|
75
|
+
prefix = " " * indent
|
|
76
|
+
|
|
77
|
+
if headers
|
|
78
|
+
header_line = table_format_row(headers, col_widths)
|
|
79
|
+
out.puts "#{prefix}#{header_line}".bold
|
|
80
|
+
out.puts "#{prefix}#{"-" * header_line.length}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
rows.each do |row|
|
|
84
|
+
out.puts "#{prefix}#{table_format_row(row, col_widths)}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def table_column_widths(rows)
|
|
91
|
+
return [] if rows.empty?
|
|
92
|
+
|
|
93
|
+
num_cols = rows.map(&:size).max
|
|
94
|
+
(0...num_cols).map do |i|
|
|
95
|
+
rows.map { |row| row[i].to_s.length }.max
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def table_format_row(row, widths)
|
|
100
|
+
row.each_with_index.map do |cell, i|
|
|
101
|
+
cell.to_s.ljust(widths[i] || 0)
|
|
102
|
+
end.join(" ")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
data/lib/smol/repl.rb
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Smol
|
|
4
|
+
class REPL
|
|
5
|
+
include Output
|
|
6
|
+
include ConfigDisplay
|
|
7
|
+
using Colors
|
|
8
|
+
|
|
9
|
+
def initialize(app, prompt:, history: true, history_file: nil, parent: nil)
|
|
10
|
+
@app = app
|
|
11
|
+
@prompt = prompt
|
|
12
|
+
@history = history
|
|
13
|
+
@history_file = history_file || File.expand_path("~/.smol_#{prompt}_history")
|
|
14
|
+
@parent = parent
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
setup_readline if @history
|
|
19
|
+
load_history if @history
|
|
20
|
+
|
|
21
|
+
show_boot_message
|
|
22
|
+
|
|
23
|
+
loop do
|
|
24
|
+
input = read_input
|
|
25
|
+
break if input.nil?
|
|
26
|
+
|
|
27
|
+
input = input.strip
|
|
28
|
+
next if input.empty?
|
|
29
|
+
|
|
30
|
+
args = input.split(/\s+/)
|
|
31
|
+
|
|
32
|
+
case args.first
|
|
33
|
+
when "exit", "quit", "q"
|
|
34
|
+
break
|
|
35
|
+
when "back"
|
|
36
|
+
break if @parent
|
|
37
|
+
warning "not in a sub-app"
|
|
38
|
+
when "help", "h", "?"
|
|
39
|
+
help
|
|
40
|
+
when "config", "c"
|
|
41
|
+
show_config
|
|
42
|
+
when "config:set"
|
|
43
|
+
set_config(args[1], args[2])
|
|
44
|
+
else
|
|
45
|
+
# Check if entering a mounted sub-app
|
|
46
|
+
mount = @app.find_mount(args.first)
|
|
47
|
+
if mount && args.size == 1
|
|
48
|
+
enter_subapp(mount, args.first)
|
|
49
|
+
else
|
|
50
|
+
dispatch(args)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
nl
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
save_history if @history
|
|
58
|
+
hint "goodbye"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def show_boot_message
|
|
64
|
+
case @app.boot
|
|
65
|
+
when :help
|
|
66
|
+
show_boot_help
|
|
67
|
+
when :minimal
|
|
68
|
+
show_boot_minimal
|
|
69
|
+
when :none
|
|
70
|
+
# nothing
|
|
71
|
+
else
|
|
72
|
+
show_boot_help
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def show_boot_help
|
|
77
|
+
banner @app.banner
|
|
78
|
+
nl
|
|
79
|
+
info @prompt.bold + " - interactive mode"
|
|
80
|
+
nl
|
|
81
|
+
help
|
|
82
|
+
nl
|
|
83
|
+
show_config
|
|
84
|
+
nl
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def show_boot_minimal
|
|
88
|
+
banner @app.banner
|
|
89
|
+
nl
|
|
90
|
+
info @prompt.bold + " - interactive mode"
|
|
91
|
+
hint "type 'help' for commands, 'exit' to quit"
|
|
92
|
+
nl
|
|
93
|
+
show_config
|
|
94
|
+
nl
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def enter_subapp(app_class, name)
|
|
98
|
+
sub_repl = REPL.new(
|
|
99
|
+
app_class,
|
|
100
|
+
prompt: "#{@prompt}:#{name}",
|
|
101
|
+
history: false,
|
|
102
|
+
parent: self
|
|
103
|
+
)
|
|
104
|
+
sub_repl.run
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def dispatch(args)
|
|
108
|
+
cmd_name, *cmd_args = args
|
|
109
|
+
klass = @app.find_command(cmd_name)
|
|
110
|
+
|
|
111
|
+
if klass.nil?
|
|
112
|
+
warning "unknown command: #{cmd_name}"
|
|
113
|
+
hint "type 'help' for available commands"
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
positional, opts = klass.parse_options(cmd_args)
|
|
118
|
+
|
|
119
|
+
if klass.args.size > positional.size
|
|
120
|
+
warning "usage: #{klass.usage}"
|
|
121
|
+
return
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
klass.new.call(*positional, **opts)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def help
|
|
128
|
+
out.puts "commands:".bold
|
|
129
|
+
|
|
130
|
+
grouped = @app.commands.group_by(&:group)
|
|
131
|
+
ungrouped = grouped.delete(nil) || []
|
|
132
|
+
|
|
133
|
+
ungrouped.each do |cmd|
|
|
134
|
+
print_command(cmd)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
grouped.keys.sort.each do |group_name|
|
|
138
|
+
nl
|
|
139
|
+
out.puts " #{group_name}:".bold
|
|
140
|
+
grouped[group_name].each do |cmd|
|
|
141
|
+
print_command(cmd, indent: 2)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
if @app.mounts.any?
|
|
146
|
+
nl
|
|
147
|
+
out.puts " sub-apps:".bold
|
|
148
|
+
@app.mounts.each do |name, app_class|
|
|
149
|
+
out.puts " #{name.ljust(28)}enter #{app_class.banner.empty? ? name : app_class.banner}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
nl
|
|
154
|
+
out.puts " config, c".ljust(32) + "show current config"
|
|
155
|
+
out.puts " config:set <key> <value>".ljust(32) + "set a config value"
|
|
156
|
+
out.puts " help, h, ?".ljust(32) + "show this help"
|
|
157
|
+
out.puts " back".ljust(32) + "return to parent app" if @parent
|
|
158
|
+
out.puts " exit, quit, q".ljust(32) + "exit"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def print_command(cmd, indent: 0)
|
|
162
|
+
prefix = " " * (indent + 1)
|
|
163
|
+
out.puts "#{prefix}#{cmd.usage.ljust(30 - indent * 2)}#{cmd.desc}"
|
|
164
|
+
out.puts "#{prefix} aliases: #{cmd.aliases.join(', ')}".dim if cmd.aliases.any?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def setup_readline
|
|
168
|
+
Readline.completion_proc = proc do |input|
|
|
169
|
+
commands = @app.commands.flat_map { |c| [c.command_name.to_s] + c.aliases.map(&:to_s) }
|
|
170
|
+
builtins = %w[help h ? config c config:set exit quit q]
|
|
171
|
+
(commands + builtins).grep(/^#{Regexp.escape(input)}/)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def load_history
|
|
176
|
+
return unless File.exist?(@history_file)
|
|
177
|
+
|
|
178
|
+
File.readlines(@history_file).each do |line|
|
|
179
|
+
Readline::HISTORY << line.chomp
|
|
180
|
+
end
|
|
181
|
+
rescue StandardError
|
|
182
|
+
# ignore history load errors
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def save_history
|
|
186
|
+
File.open(@history_file, "w") do |f|
|
|
187
|
+
Readline::HISTORY.to_a.last(1000).each do |line|
|
|
188
|
+
f.puts line
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
rescue StandardError
|
|
192
|
+
# ignore history save errors
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def read_input
|
|
196
|
+
if @history
|
|
197
|
+
prompt_str = "#{@prompt}> "
|
|
198
|
+
line = Readline.readline(prompt_str, true)
|
|
199
|
+
Readline::HISTORY.pop if line&.strip&.empty?
|
|
200
|
+
line
|
|
201
|
+
else
|
|
202
|
+
Smol.output.print "#{@prompt}> ".yellow
|
|
203
|
+
Smol.input.gets
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
data/lib/smol/version.rb
ADDED
data/lib/smol.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "logger"
|
|
4
|
+
require "readline"
|
|
5
|
+
|
|
6
|
+
require "smol/version"
|
|
7
|
+
require "smol/colors"
|
|
8
|
+
require "smol/coercion"
|
|
9
|
+
require "smol/app_lookup"
|
|
10
|
+
require "smol/check_result"
|
|
11
|
+
require "smol/output"
|
|
12
|
+
require "smol/input"
|
|
13
|
+
require "smol/config"
|
|
14
|
+
require "smol/config_display"
|
|
15
|
+
require "smol/check"
|
|
16
|
+
require "smol/command"
|
|
17
|
+
require "smol/app"
|
|
18
|
+
require "smol/repl"
|
|
19
|
+
require "smol/cli"
|
|
20
|
+
|
|
21
|
+
module Smol
|
|
22
|
+
class Error < StandardError; end
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
attr_writer :output, :input, :logger, :verbose, :quiet
|
|
26
|
+
|
|
27
|
+
def output
|
|
28
|
+
@output ||= $stdout
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def input
|
|
32
|
+
@input ||= $stdin
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def logger
|
|
36
|
+
@logger ||= Logger.new($stderr, level: Logger::WARN)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def verbose?
|
|
40
|
+
@verbose || ENV["VERBOSE"] == "1" || ENV["VERBOSE"] == "true"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def quiet?
|
|
44
|
+
@quiet || ENV["QUIET"] == "1" || ENV["QUIET"] == "true"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def debug?
|
|
48
|
+
@debug || ENV["DEBUG"] == "1" || ENV["DEBUG"] == "true"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def debug=(value)
|
|
52
|
+
@debug = value
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: smol
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Josh Brody
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: logger
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: readline
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
description: Build CLI tools with commands, checks, and configuration. Supports both
|
|
41
|
+
single-command execution and interactive REPL mode.
|
|
42
|
+
email:
|
|
43
|
+
- gems@josh.mn
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- CHANGELOG.md
|
|
49
|
+
- LICENSE.txt
|
|
50
|
+
- README.md
|
|
51
|
+
- Rakefile
|
|
52
|
+
- lib/smol.rb
|
|
53
|
+
- lib/smol/app.rb
|
|
54
|
+
- lib/smol/app_lookup.rb
|
|
55
|
+
- lib/smol/check.rb
|
|
56
|
+
- lib/smol/check_result.rb
|
|
57
|
+
- lib/smol/cli.rb
|
|
58
|
+
- lib/smol/coercion.rb
|
|
59
|
+
- lib/smol/colors.rb
|
|
60
|
+
- lib/smol/command.rb
|
|
61
|
+
- lib/smol/config.rb
|
|
62
|
+
- lib/smol/config_display.rb
|
|
63
|
+
- lib/smol/input.rb
|
|
64
|
+
- lib/smol/output.rb
|
|
65
|
+
- lib/smol/repl.rb
|
|
66
|
+
- lib/smol/version.rb
|
|
67
|
+
homepage: https://github.com/joshmn/smol
|
|
68
|
+
licenses:
|
|
69
|
+
- MIT
|
|
70
|
+
metadata:
|
|
71
|
+
allowed_push_host: https://rubygems.org
|
|
72
|
+
homepage_uri: https://github.com/joshmn/smol
|
|
73
|
+
source_code_uri: https://github.com/joshmn/smol
|
|
74
|
+
changelog_uri: https://github.com/joshmn/smol/blob/main/CHANGELOG.md
|
|
75
|
+
rdoc_options: []
|
|
76
|
+
require_paths:
|
|
77
|
+
- lib
|
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '2.4'
|
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
84
|
+
requirements:
|
|
85
|
+
- - ">="
|
|
86
|
+
- !ruby/object:Gem::Version
|
|
87
|
+
version: '0'
|
|
88
|
+
requirements: []
|
|
89
|
+
rubygems_version: 4.0.1
|
|
90
|
+
specification_version: 4
|
|
91
|
+
summary: A small, zero-dependency CLI and REPL framework for Ruby
|
|
92
|
+
test_files: []
|