run_kit 0.1.1 → 0.1.3
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/.rubocop.yml +0 -1
- data/Gemfile +1 -1
- data/README.md +17 -0
- data/lib/run_kit/core_ext.rb +5 -10
- data/lib/run_kit/options/config.rb +12 -12
- data/lib/run_kit/options/flag.rb +38 -16
- data/lib/run_kit/options/help.rb +7 -2
- data/lib/run_kit/options/parser.rb +58 -39
- data/lib/run_kit.rb +8 -1
- data/run_kit.gemspec +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1876301102a9221f5207f024bcd108a95d3547cf96d270f475700959d8209cb6
|
|
4
|
+
data.tar.gz: b21d96ce661cbe5e45f2de8b6bf9cb1718d2bb7305bce4e15773aa7a9c4882e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a82974e7beaa687457ff4b8555e8d1b38a284c89d4ef756755bbccc92c16ff857cdfe9d5083620bb53265d6b04de43d7fdca168559c4bef25ef99c052e98fbae
|
|
7
|
+
data.tar.gz: 43946b37bb5f6a155b5eb2d27220afa96df7705827db9e0a7ddb4aa5b86f6234629908436cdd288ed20cc8b751c48c413f3441d265cf4c8925de373629f7e99f
|
data/.rubocop.yml
CHANGED
|
@@ -35,7 +35,6 @@ Naming/MemoizedInstanceVariableName: { Enabled: true } # clean memo ivars
|
|
|
35
35
|
Naming/MethodName: { Enabled: true } # keep method names conventional
|
|
36
36
|
Performance/MapCompact: { Enabled: true } # filter_map-ish style
|
|
37
37
|
Performance/RegexpMatch: { Enabled: false } # local style is fine
|
|
38
|
-
Performance/SelectMap: { Enabled: true } # filter_map-ish style
|
|
39
38
|
Style/BlockDelimiters: { Enabled: true } # do/end vs braces
|
|
40
39
|
Style/ClassAndModuleChildren: { Enabled: true, EnforcedStyle: nested } # explicit nesting
|
|
41
40
|
Style/ClassMethodsDefinitions: { Enabled: true } # avoid "class << self"
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -30,6 +30,15 @@ Automatic `--help` for the above. Uses color and wraps to terminal:
|
|
|
30
30
|
|
|
31
31
|
<img width="379" height="108" alt="image" src="https://github.com/user-attachments/assets/3d11c8ca-6b57-4dec-9c11-28bf23162ded" />
|
|
32
32
|
|
|
33
|
+
Flags can also read from ENV:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
o.bool "--force", env: true # ENV["FORCE"]
|
|
37
|
+
o.str "--token", env: "API_TOKEN" # ENV["API_TOKEN"]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Configured variables appear in `--help`. Command-line values override ENV, which overrides defaults. Boolean ENV values accept `true/1/yes/on` and `false/0/no/off/empty`, ignoring case.
|
|
41
|
+
|
|
33
42
|
## RunKit::Shell
|
|
34
43
|
|
|
35
44
|
`RunKit::Shell` is a mixin with many helpers for bin scripts:
|
|
@@ -102,6 +111,14 @@ Note: There has been some effort to get the Pathname helpers into Ruby itself, w
|
|
|
102
111
|
|
|
103
112
|
### Changelog
|
|
104
113
|
|
|
114
|
+
#### 0.1.3 (Sep 2026)
|
|
115
|
+
|
|
116
|
+
- move PROGBAR constant into RunKit::
|
|
117
|
+
|
|
118
|
+
#### 0.1.2 (Sep 2026)
|
|
119
|
+
|
|
120
|
+
- allow options to read from ENV
|
|
121
|
+
|
|
105
122
|
#### 0.1.1 Sep 2026
|
|
106
123
|
|
|
107
124
|
- add title to progressbar default
|
data/lib/run_kit/core_ext.rb
CHANGED
|
@@ -34,20 +34,15 @@ module RunKit
|
|
|
34
34
|
# each.with_progresbar
|
|
35
35
|
module Enumerator
|
|
36
36
|
def with_progressbar(options = {}, &block)
|
|
37
|
-
|
|
38
|
-
format: "%t: %j%% %B #{RunKit::Term.paint_ansi("%c/%u %e", RunKit::Term.ansi256_fg(242))}",
|
|
39
|
-
progress_mark: RunKit::Term.paint_ansi("━", RunKit::Term.ansi256_fg(46)),
|
|
40
|
-
remainder_mark: RunKit::Term.paint_ansi("━", RunKit::Term.ansi256_fg(237)),
|
|
37
|
+
options = RunKit::PROGRESSBAR.merge(
|
|
41
38
|
output: $stdout.isatty ? $stdout : $stderr,
|
|
42
|
-
total: size
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
options = defaults.merge(options)
|
|
39
|
+
total: size
|
|
40
|
+
).merge(options)
|
|
46
41
|
|
|
47
42
|
return enum_for(__method__) if !block
|
|
48
43
|
|
|
49
|
-
if !options[:hide]
|
|
50
|
-
|
|
44
|
+
bar = if !options[:hide]
|
|
45
|
+
ProgressBar.create(options)
|
|
51
46
|
end
|
|
52
47
|
RunKit::Term.with_hidden_cursor(options[:output]) do
|
|
53
48
|
each do
|
|
@@ -37,28 +37,28 @@ module RunKit
|
|
|
37
37
|
# flags
|
|
38
38
|
#
|
|
39
39
|
|
|
40
|
-
def bool(*opts, default: nil, required: false)
|
|
41
|
-
add_flag(Flag.new(:bool, opts, default:, required:))
|
|
40
|
+
def bool(*opts, default: nil, required: false, env: nil)
|
|
41
|
+
add_flag(Flag.new(:bool, opts, default:, required:, env:))
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def float(*opts, default: nil, required: false, choices: nil)
|
|
45
|
-
add_flag(Flag.new(:float, opts, default:, required:, choices:))
|
|
44
|
+
def float(*opts, default: nil, required: false, choices: nil, env: nil)
|
|
45
|
+
add_flag(Flag.new(:float, opts, default:, required:, choices:, env:))
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
def int(*opts, default: nil, required: false, choices: nil)
|
|
49
|
-
add_flag(Flag.new(:int, opts, default:, required:, choices:))
|
|
48
|
+
def int(*opts, default: nil, required: false, choices: nil, env: nil)
|
|
49
|
+
add_flag(Flag.new(:int, opts, default:, required:, choices:, env:))
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
def path(*opts, default: nil, required: false, choices: nil)
|
|
53
|
-
add_flag(Flag.new(:path, opts, default:, required:, choices:))
|
|
52
|
+
def path(*opts, default: nil, required: false, choices: nil, env: nil)
|
|
53
|
+
add_flag(Flag.new(:path, opts, default:, required:, choices:, env:))
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def str(*opts, default: nil, required: false, choices: nil)
|
|
57
|
-
add_flag(Flag.new(:str, opts, default:, required:, choices:))
|
|
56
|
+
def str(*opts, default: nil, required: false, choices: nil, env: nil)
|
|
57
|
+
add_flag(Flag.new(:str, opts, default:, required:, choices:, env:))
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
def sym(*opts, default: nil, required: false, choices: nil)
|
|
61
|
-
add_flag(Flag.new(:sym, opts, default:, required:, choices:))
|
|
60
|
+
def sym(*opts, default: nil, required: false, choices: nil, env: nil)
|
|
61
|
+
add_flag(Flag.new(:sym, opts, default:, required:, choices:, env:))
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# long-form aliases
|
data/lib/run_kit/options/flag.rb
CHANGED
|
@@ -12,12 +12,14 @@ module RunKit
|
|
|
12
12
|
# --no-foo
|
|
13
13
|
NEGATE_RE = /\A--no-(\w[\w-]*)\z/
|
|
14
14
|
|
|
15
|
+
TRUE_ENV = %w[1 true yes on]
|
|
16
|
+
FALSE_ENV = ["", "0", "false", "no", "off"]
|
|
15
17
|
KINDS = %i[bool float int path str sym]
|
|
16
18
|
|
|
17
|
-
attr_reader
|
|
19
|
+
attr_reader(*%i[choices default env help kind meta required switches])
|
|
18
20
|
|
|
19
21
|
# ctor
|
|
20
|
-
def initialize(kind, opts, default: nil, required: false, choices: nil)
|
|
22
|
+
def initialize(kind, opts, default: nil, required: false, choices: nil, env: nil)
|
|
21
23
|
@choices, @kind, @required = choices, kind, required
|
|
22
24
|
|
|
23
25
|
# extract @help from last string
|
|
@@ -28,6 +30,7 @@ module RunKit
|
|
|
28
30
|
|
|
29
31
|
# Extract meta from the final switch, if written as `--port <int>`.
|
|
30
32
|
@meta = build_meta
|
|
33
|
+
@env = (env == true) ? key.to_s.upcase : env
|
|
31
34
|
|
|
32
35
|
# default, with some special handling for bool
|
|
33
36
|
@default = default
|
|
@@ -50,22 +53,21 @@ module RunKit
|
|
|
50
53
|
end
|
|
51
54
|
|
|
52
55
|
raise Error, "option '#{switch}' requires a value" if !param
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
when :float then Float(param)
|
|
56
|
-
when :int then Integer(param, 10)
|
|
57
|
-
when :path then Pathname.new(param)
|
|
58
|
-
when :str then param
|
|
59
|
-
when :sym then param.to_sym
|
|
60
|
-
end
|
|
61
|
-
rescue ArgumentError
|
|
62
|
-
raise Error, "invalid value '#{param}' for option '#{switch}'"
|
|
63
|
-
end
|
|
56
|
+
parse_value(param, "option '#{switch}'")
|
|
57
|
+
end
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
# Parse a value supplied by ENV[$SOMETHING]
|
|
60
|
+
def parse_env(param)
|
|
61
|
+
source = "environment variable '#{env}'"
|
|
62
|
+
|
|
63
|
+
# special handling for bools
|
|
64
|
+
if bool?
|
|
65
|
+
return true if TRUE_ENV.include?(param.downcase)
|
|
66
|
+
return false if FALSE_ENV.include?(param.downcase)
|
|
67
|
+
raise Error, "invalid value '#{param}' for #{source}"
|
|
67
68
|
end
|
|
68
|
-
|
|
69
|
+
|
|
70
|
+
parse_value(param, source)
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
# one-liners
|
|
@@ -96,6 +98,7 @@ module RunKit
|
|
|
96
98
|
raise ArgumentError, "required must be true or false" unless required == true || required == false
|
|
97
99
|
raise ArgumentError, "required flags cannot have defaults" if required && default != nil
|
|
98
100
|
raise ArgumentError, "invalid default #{default.inspect} for #{kind}" if default != nil && !allowed?(default)
|
|
101
|
+
raise ArgumentError, "env must be true or a string" unless env.nil? || env.is_a?(String)
|
|
99
102
|
|
|
100
103
|
# choices
|
|
101
104
|
if choices
|
|
@@ -130,6 +133,25 @@ module RunKit
|
|
|
130
133
|
when :sym then candidate.is_a?(Symbol)
|
|
131
134
|
end
|
|
132
135
|
end
|
|
136
|
+
|
|
137
|
+
def parse_value(param, source)
|
|
138
|
+
parsed = begin
|
|
139
|
+
case kind
|
|
140
|
+
when :float then Float(param)
|
|
141
|
+
when :int then Integer(param, 10)
|
|
142
|
+
when :path then Pathname.new(param)
|
|
143
|
+
when :str then param
|
|
144
|
+
when :sym then param.to_sym
|
|
145
|
+
end
|
|
146
|
+
rescue ArgumentError
|
|
147
|
+
raise Error, "invalid value '#{param}' for #{source}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
if choices && !choices.include?(parsed)
|
|
151
|
+
raise Error, "invalid value '#{parsed}' for #{source}, must be one of #{choices.join(", ")}"
|
|
152
|
+
end
|
|
153
|
+
parsed
|
|
154
|
+
end
|
|
133
155
|
end
|
|
134
156
|
end
|
|
135
157
|
end
|
data/lib/run_kit/options/help.rb
CHANGED
|
@@ -36,10 +36,15 @@ module RunKit
|
|
|
36
36
|
buf << label
|
|
37
37
|
|
|
38
38
|
# right
|
|
39
|
-
|
|
39
|
+
help = flag.help
|
|
40
|
+
if flag.env
|
|
41
|
+
env_help = "[env: #{flag.env}]"
|
|
42
|
+
help = help ? "#{help} #{env_help}" : env_help
|
|
43
|
+
end
|
|
44
|
+
if help
|
|
40
45
|
buf << " " * (label_width - Term.width(label) + 2)
|
|
41
46
|
indent = INDENT + label_width + 2
|
|
42
|
-
buf << Term.wrap(
|
|
47
|
+
buf << Term.wrap(help, width - indent).gsub("\n", "\n#{" " * indent}")
|
|
43
48
|
end
|
|
44
49
|
buf << "\n"
|
|
45
50
|
end
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
module RunKit
|
|
7
7
|
module Options
|
|
8
8
|
class Parser
|
|
9
|
-
attr_reader :config
|
|
9
|
+
attr_reader :config
|
|
10
10
|
|
|
11
11
|
def initialize(config)
|
|
12
12
|
@config = config
|
|
@@ -14,9 +14,24 @@ module RunKit
|
|
|
14
14
|
|
|
15
15
|
# Reset transient state, parse argv, and assemble the result.
|
|
16
16
|
def parse(argv)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
# 1. naked?
|
|
18
|
+
raise NakedRequested if config.naked? && argv.empty?
|
|
19
|
+
|
|
20
|
+
# 2. Parse argv. We do this first, because other things can raise and
|
|
21
|
+
# --help should trump other issues.
|
|
22
|
+
argv_options = parse_argv(argv)
|
|
23
|
+
|
|
24
|
+
# 3. defaults => ENV => ARGV
|
|
25
|
+
options = {}.merge(config.defaults, parse_env, argv_options)
|
|
26
|
+
|
|
27
|
+
# 4. validate final options
|
|
28
|
+
validate!(options)
|
|
29
|
+
|
|
30
|
+
# success! add predicate? keys
|
|
31
|
+
config.flags.select(&:bool?).map(&:key).each do
|
|
32
|
+
options[:"#{_1}?"] = options[_1] if options.key?(_1)
|
|
33
|
+
end
|
|
34
|
+
|
|
20
35
|
options
|
|
21
36
|
end
|
|
22
37
|
|
|
@@ -26,42 +41,36 @@ module RunKit
|
|
|
26
41
|
# main parser
|
|
27
42
|
#
|
|
28
43
|
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
def parse_argv(argv)
|
|
45
|
+
{}.tap do |result|
|
|
46
|
+
# any non-flags we find below
|
|
47
|
+
operands = []
|
|
48
|
+
|
|
49
|
+
# process argv as queue
|
|
50
|
+
queue = argv.dup
|
|
51
|
+
while (item = queue.shift)
|
|
52
|
+
case item
|
|
53
|
+
when Flag::SWITCH_RE, Flag::INLINE_RE then result.merge!(parse_switch(item, Regexp.last_match, queue))
|
|
54
|
+
when /\A-[^-]/ then result.merge!(parse_smashed(item, queue))
|
|
55
|
+
when "", /\A[^-]/ then operands << item
|
|
56
|
+
when "--" then break operands.concat(queue)
|
|
57
|
+
else; raise Error, "unexpected argument '#{item}' found"
|
|
58
|
+
end
|
|
43
59
|
end
|
|
44
|
-
end
|
|
45
60
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
options[:"#{_1}?"] = options[_1] if options.key?(_1)
|
|
49
|
-
end
|
|
61
|
+
# positionals
|
|
62
|
+
config.positionals.each { result[_1.key] = operands.shift }
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
options[_1.key] = operands.shift
|
|
64
|
+
# _args
|
|
65
|
+
result[:_args] = operands
|
|
54
66
|
end
|
|
55
|
-
|
|
56
|
-
# _args
|
|
57
|
-
options[:_args] = operands
|
|
58
67
|
end
|
|
59
68
|
|
|
60
69
|
#
|
|
61
70
|
# -x or -x=123 or --xyz or --xyz=123 or --no-xyz
|
|
62
71
|
#
|
|
63
72
|
|
|
64
|
-
def parse_switch(item, match)
|
|
73
|
+
def parse_switch(item, match, queue)
|
|
65
74
|
switch = "-#{match[1]}"
|
|
66
75
|
param = match[2]
|
|
67
76
|
separator = param ? "=" : ""
|
|
@@ -70,15 +79,13 @@ module RunKit
|
|
|
70
79
|
if (flag = config.flag(switch))
|
|
71
80
|
builtin!(flag)
|
|
72
81
|
param = queue.shift if flag.takes_param? && separator.empty?
|
|
73
|
-
|
|
74
|
-
return
|
|
82
|
+
return {flag.key => flag.parse(switch, param)}
|
|
75
83
|
end
|
|
76
84
|
|
|
77
85
|
# --no-xyz?
|
|
78
86
|
if (neg = find_negated_flag(switch))
|
|
79
87
|
raise Error, "option '#{item}' does not take a value" if separator == "="
|
|
80
|
-
|
|
81
|
-
return
|
|
88
|
+
return {neg.key => false}
|
|
82
89
|
end
|
|
83
90
|
|
|
84
91
|
raise Error, "unexpected argument '#{item}' found"
|
|
@@ -90,7 +97,8 @@ module RunKit
|
|
|
90
97
|
|
|
91
98
|
# Expand short-switch groups such as `-qv`. A parameter-taking switch ends
|
|
92
99
|
# the group and consumes either its attached suffix or the next queue item.
|
|
93
|
-
def parse_smashed(group)
|
|
100
|
+
def parse_smashed(group, queue)
|
|
101
|
+
result = {}
|
|
94
102
|
(1...group.length).each do |idx|
|
|
95
103
|
switch = "-#{group[idx]}"
|
|
96
104
|
flag = config.flag(switch)
|
|
@@ -104,13 +112,24 @@ module RunKit
|
|
|
104
112
|
else
|
|
105
113
|
queue.shift
|
|
106
114
|
end
|
|
107
|
-
|
|
108
|
-
return
|
|
115
|
+
result[flag.key] = flag.parse(switch, param)
|
|
116
|
+
return result
|
|
109
117
|
end
|
|
110
118
|
|
|
111
119
|
# bool
|
|
112
|
-
|
|
120
|
+
result[flag.key] = true
|
|
113
121
|
end
|
|
122
|
+
result
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
#
|
|
126
|
+
# env
|
|
127
|
+
#
|
|
128
|
+
|
|
129
|
+
def parse_env
|
|
130
|
+
config.flags.select { _1.env && ENV.key?(_1.env) }.map do |flag|
|
|
131
|
+
[flag.key, flag.parse_env(ENV[flag.env])]
|
|
132
|
+
end.to_h
|
|
114
133
|
end
|
|
115
134
|
|
|
116
135
|
#
|
|
@@ -129,7 +148,7 @@ module RunKit
|
|
|
129
148
|
end
|
|
130
149
|
end
|
|
131
150
|
|
|
132
|
-
def validate!
|
|
151
|
+
def validate!(options)
|
|
133
152
|
config.required.each do
|
|
134
153
|
raise Error, "required option '#{_1.switch}' is missing" if !options.key?(_1.key)
|
|
135
154
|
end
|
data/lib/run_kit.rb
CHANGED
|
@@ -16,7 +16,14 @@ require_relative "run_kit/term"
|
|
|
16
16
|
require_relative "run_kit/options"
|
|
17
17
|
require_relative "run_kit/shell"
|
|
18
18
|
|
|
19
|
-
# handy entry point for RunKit::Options
|
|
20
19
|
module RunKit
|
|
20
|
+
PROGRESSBAR = {
|
|
21
|
+
format: "%t: %j%% %B #{Term.paint_ansi("%c/%u %e", Term.ansi256_fg(242))}",
|
|
22
|
+
progress_mark: Term.paint_ansi("━", Term.ansi256_fg(46)),
|
|
23
|
+
remainder_mark: Term.paint_ansi("━", Term.ansi256_fg(237)),
|
|
24
|
+
length: 72,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# handy entry point for RunKit::Options
|
|
21
28
|
def self.parse(...) = Options.parse(...)
|
|
22
29
|
end
|
data/run_kit.gemspec
CHANGED