philiprehberger-cli_kit 0.3.1 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 62b7b5d92fab1ecd92a23be447b3fbc30954178eee755845f9a7f37e6de6e079
4
- data.tar.gz: 9ea01598fc9e677cb480d92b66c00594df89dced14179144bd630052a97ad73c
3
+ metadata.gz: 895e4a8ff9edcfa13591e60cd91454c81840404c61b724e3394d234b71f8e496
4
+ data.tar.gz: b789d3b41ab39145500841661603728c8edccc438ab577319c6166bdf27230cb
5
5
  SHA512:
6
- metadata.gz: 3fd67e90ab104d28e6a759c360fc7609f4d0fee691bf0a0f21d828c54ca38b56974abd10e965293c96b7e98a9152e8c73fc90f6e1d5afcbe3a0d648386bbdc2a
7
- data.tar.gz: '0459248772c329302ed8fdd2c798cb153e29e131f8f93656250a75fd7537edfef6f37c10c48b829533dafe33f272ed5e824d7a0958135cc8cb7adfebbbbe8360'
6
+ metadata.gz: 6b708dc9eadf82852b3d683f32dd4adeeaae2bc0f3985ce2c338930a58cc1cb8bbe2cff17a929578907ffe68ae1c63a38ff115129fefdb68a531a18c467d0650
7
+ data.tar.gz: 94e8d68950132d59d2ed894d356374269989d5f73db2837897f957cc3fd70011f5f1b4c96f8ba75554b7069c65900c1385bf357885ba925346009da154ba011f
data/CHANGELOG.md CHANGED
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] - 2026-04-25
11
+
12
+ ### Added
13
+ - ANSI color helpers via `CliKit.color`, `CliKit.bold`, `CliKit.dim` and the `CliKit::Colorize` module
14
+ - Automatic disable when stdout is not a TTY or `NO_COLOR` is set
15
+
16
+ ## [0.4.0] - 2026-04-18
17
+
18
+ ### Added
19
+ - `option :name, required: true` — DSL flag that raises `CliKit::Error` at parse time when the option is omitted; help output appends `(required)` to the option's description
20
+
10
21
  ## [0.3.1] - 2026-04-15
11
22
 
12
23
  ### Changed
@@ -63,7 +74,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
63
74
  - Animated spinner for long-running operations
64
75
  - Positional argument collection
65
76
 
66
- [Unreleased]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.3.1...HEAD
77
+ [Unreleased]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.5.0...HEAD
78
+ [0.5.0]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.4.0...v0.5.0
79
+ [0.4.0]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.3.1...v0.4.0
67
80
  [0.3.1]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.3.0...v0.3.1
68
81
  [0.3.0]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.2.1...v0.3.0
69
82
  [0.2.1]: https://github.com/philiprehberger/rb-cli-kit/compare/v0.2.0...v0.2.1
data/README.md CHANGED
@@ -105,6 +105,20 @@ port = Philiprehberger::CliKit.ask('Port:', error: 'Must be a number') do |answe
105
105
  end
106
106
  ```
107
107
 
108
+ ### Required options
109
+
110
+ ```ruby
111
+ result = Philiprehberger::CliKit.parse(ARGV) do
112
+ option :env, short: :e, required: true, desc: 'Target environment'
113
+ end
114
+
115
+ # Invoked without --env raises Philiprehberger::CliKit::Error:
116
+ # Missing required option(s): --env
117
+ #
118
+ # Help text appends "(required)" to the option's description:
119
+ # -e, --env VALUE Target environment (required)
120
+ ```
121
+
108
122
  ### Repeatable Options
109
123
 
110
124
  ```ruby
@@ -163,6 +177,17 @@ data = Philiprehberger::CliKit.spinner('Loading data...') do
163
177
  end
164
178
  ```
165
179
 
180
+ ### Color Output
181
+
182
+ Colors are auto-disabled when stdout is not a TTY or the `NO_COLOR` environment variable is set.
183
+
184
+ ```ruby
185
+ require "philiprehberger/cli_kit"
186
+
187
+ puts Philiprehberger::CliKit.color('OK', :green)
188
+ puts Philiprehberger::CliKit.bold('Important')
189
+ ```
190
+
166
191
  ## API
167
192
 
168
193
  | Method | Description |
@@ -175,7 +200,11 @@ end
175
200
  | `.select(message, choices)` | Present numbered menu and return one selection |
176
201
  | `.multi_select(message, choices, defaults:)` | Present numbered menu and return multiple selections |
177
202
  | `.spinner(message) { ... }` | Show spinner during block execution |
203
+ | `.color(text, name)` | Wrap text in ANSI color (no-op when not a TTY or NO_COLOR set) |
204
+ | `.bold(text)` | Wrap text in ANSI bold |
205
+ | `.dim(text)` | Wrap text in ANSI dim |
178
206
  | `Parser#option(name, multi: true)` | Collect repeated option values into an array |
207
+ | `Parser#option(name, required: true)` | Raise `CliKit::Error` at parse time when the option is omitted |
179
208
  | `Parser#flags` | Hash of boolean flag values |
180
209
  | `Parser#options` | Hash of option values (arrays when `multi: true`) |
181
210
  | `Parser#arguments` | Array of positional arguments |
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Philiprehberger
4
+ module CliKit
5
+ # ANSI color and style helpers. Output is auto-disabled when stdout is not a
6
+ # TTY or the NO_COLOR environment variable is set (per https://no-color.org).
7
+ module Colorize
8
+ CODES = {
9
+ red: 31, green: 32, yellow: 33, blue: 34,
10
+ magenta: 35, cyan: 36, white: 37, gray: 90
11
+ }.freeze
12
+
13
+ module_function
14
+
15
+ # @return [Boolean] true when ANSI escape codes should be emitted
16
+ def enabled?
17
+ return false if ENV.key?('NO_COLOR')
18
+
19
+ $stdout.tty?
20
+ end
21
+
22
+ # Wraps text in an ANSI color escape, or returns it untouched when colors
23
+ # are disabled.
24
+ #
25
+ # @param text [String]
26
+ # @param name [Symbol] one of :red, :green, :yellow, :blue, :magenta, :cyan, :white, :gray
27
+ # @return [String]
28
+ def color(text, name)
29
+ return text unless enabled?
30
+
31
+ code = CODES.fetch(name) { raise ArgumentError, "unknown color: #{name.inspect}" }
32
+ "\e[#{code}m#{text}\e[0m"
33
+ end
34
+
35
+ # @param text [String]
36
+ # @return [String]
37
+ def bold(text)
38
+ return text unless enabled?
39
+
40
+ "\e[1m#{text}\e[0m"
41
+ end
42
+
43
+ # @param text [String]
44
+ # @return [String]
45
+ def dim(text)
46
+ return text unless enabled?
47
+
48
+ "\e[2m#{text}\e[0m"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -13,6 +13,7 @@ module Philiprehberger
13
13
  @flags = {}
14
14
  @options = {}
15
15
  @arguments = []
16
+ @supplied_options = []
16
17
  @command_name = nil
17
18
  @program_name = nil
18
19
  end
@@ -35,9 +36,12 @@ module Philiprehberger
35
36
  # @param default [Object, nil] default value
36
37
  # @param desc [String, nil] description for help text
37
38
  # @param multi [Boolean] when true, collect repeated values into an array
39
+ # @param required [Boolean] when true, raise if the option is not supplied at parse time
38
40
  # @return [void]
39
- def option(name, short: nil, default: nil, desc: nil, multi: false)
40
- @option_definitions[name] = { short: short, default: default, desc: desc, multi: multi }
41
+ def option(name, short: nil, default: nil, desc: nil, multi: false, required: false)
42
+ @option_definitions[name] = {
43
+ short: short, default: default, desc: desc, multi: multi, required: required
44
+ }
41
45
  @options[name] = multi ? [] : default
42
46
  end
43
47
 
@@ -131,6 +135,7 @@ module Philiprehberger
131
135
  @arguments << arg
132
136
  end
133
137
  end
138
+ validate_required_options!
134
139
  self
135
140
  end
136
141
 
@@ -172,6 +177,7 @@ module Philiprehberger
172
177
 
173
178
  def assign_option(name, value)
174
179
  defn = @option_definitions[name]
180
+ @supplied_options << name
175
181
  if defn && defn[:multi]
176
182
  @options[name] = [] unless @options[name].is_a?(Array)
177
183
  @options[name] << value unless value.nil?
@@ -180,6 +186,16 @@ module Philiprehberger
180
186
  end
181
187
  end
182
188
 
189
+ def validate_required_options!
190
+ missing = @option_definitions.each_with_object([]) do |(name, defn), acc|
191
+ acc << name if defn[:required] && !@supplied_options.include?(name)
192
+ end
193
+ return if missing.empty?
194
+
195
+ names = missing.map { |n| "--#{n.to_s.tr('_', '-')}" }.join(', ')
196
+ raise Error, "Missing required option(s): #{names}"
197
+ end
198
+
183
199
  def format_flag_help(name, defn)
184
200
  long = "--#{name.to_s.tr('_', '-')}"
185
201
  if defn[:short]
@@ -204,8 +220,10 @@ module Philiprehberger
204
220
  else
205
221
  label = " #{long}"
206
222
  end
207
- if defn[:desc]
208
- "#{label.ljust(24)}#{defn[:desc]}"
223
+ desc = defn[:desc]
224
+ desc = desc ? "#{desc} (required)" : '(required)' if defn[:required]
225
+ if desc
226
+ "#{label.ljust(24)}#{desc}"
209
227
  else
210
228
  label
211
229
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module CliKit
5
- VERSION = '0.3.1'
5
+ VERSION = '0.5.0'
6
6
  end
7
7
  end
@@ -5,6 +5,7 @@ require_relative 'cli_kit/parser'
5
5
  require_relative 'cli_kit/prompt'
6
6
  require_relative 'cli_kit/spinner'
7
7
  require_relative 'cli_kit/menu'
8
+ require_relative 'cli_kit/colorize'
8
9
 
9
10
  module Philiprehberger
10
11
  module CliKit
@@ -105,5 +106,26 @@ module Philiprehberger
105
106
  def self.multi_select(message, choices, defaults: [], input: $stdin, output: $stdout)
106
107
  Menu.multi_select(message, choices, defaults: defaults, input: input, output: output)
107
108
  end
109
+
110
+ # Wraps text in ANSI color (auto-disabled when not a TTY or NO_COLOR is set).
111
+ #
112
+ # @param text [String]
113
+ # @param name [Symbol]
114
+ # @return [String]
115
+ def self.color(text, name)
116
+ Colorize.color(text, name)
117
+ end
118
+
119
+ # @param text [String]
120
+ # @return [String]
121
+ def self.bold(text)
122
+ Colorize.bold(text)
123
+ end
124
+
125
+ # @param text [String]
126
+ # @return [String]
127
+ def self.dim(text)
128
+ Colorize.dim(text)
129
+ end
108
130
  end
109
131
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-cli_kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-15 00:00:00.000000000 Z
11
+ date: 2026-04-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Lightweight CLI toolkit combining argument parsing with flags and options,
14
14
  interactive prompts with confirmation, and animated spinners for long-running operations.
@@ -22,6 +22,7 @@ files:
22
22
  - LICENSE
23
23
  - README.md
24
24
  - lib/philiprehberger/cli_kit.rb
25
+ - lib/philiprehberger/cli_kit/colorize.rb
25
26
  - lib/philiprehberger/cli_kit/menu.rb
26
27
  - lib/philiprehberger/cli_kit/parser.rb
27
28
  - lib/philiprehberger/cli_kit/prompt.rb