woad 0.0.4 → 0.1.0.pre.alpha1

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: d04768b6af41bb4eb8fd01c6f64481cd55de6af32e29f1adff25b515b6b80228
4
- data.tar.gz: 129c71c6f830e9e668ccacf89c2760fdde40d328e8e0aa7c6f0718a2b137c323
3
+ metadata.gz: 0c92ea501cd703ffff5218755f045f72f865cd372bcc26247719846361f79e10
4
+ data.tar.gz: 7e6ab3bf38cb7d3be7fba3306ec830cb35864260ac4e228295547b37938ea126
5
5
  SHA512:
6
- metadata.gz: 030f5b04de173b86d21f0f85e6085ab5705fed414417dc9b81a30243d2e050fbb4bd74f3b2f85973c9c8154b171a3627f20de066de0ab6dc51af4fd4c77fe379
7
- data.tar.gz: ecbbee9a1407aa0d6740c82871962a4f85d93f32cea386d09b1265a5b95e849ddead1f897fb7fd1102f910a03adeb96873421a5acf6465d5d30a4dbc661e459b
6
+ metadata.gz: ec4e1760af137d4f4446b98ab44aeb5f78f90ae550c8ff586c03a90d41ece48971b84654b8d27cba68a921c559dc7d3c14b410f2a7a5e9edff8c06f1e6d30c10
7
+ data.tar.gz: cd8c8d93d721bee99525e0f9d499437c039d1b06cb1cfd050300601a17ca93e414843ac0c3a266d82f9c02cbb5f42b81af93e79de1e20aca7619f15f1d557630
data/CHANGES.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # woad.Ruby - Changes <!-- omit in toc -->
2
2
 
3
3
 
4
+ ## 0.1.0-alpha1 - 16th November 2026
5
+
6
+ * added `BLINKING` and `BOLD`, byte-for-byte with **woad** **0.3.0**;
7
+ * added cached `colour_policy`, `refresh`, `supports_colour`, and `seq_for` for the portable colour contract (Unix attachment and `TERM=dumb`; no console probe);
8
+ * added **test/component/tc_colour_policy.rb** (decision rows 1-22) and **examples/conditional**;
9
+ * left Windows virtual-terminal mode and mintty PTY recognition for a later alpha;
10
+ * moved the fixed SGR sequences into **Woad::Sequences**, mixed into **Woad**;
11
+ * documented the colour environment variables and their accepted forms in **README.md**;
12
+
13
+
4
14
  ## 0.0.4 - 30th August 2026
5
15
 
6
16
  * added shared **.vimrc** editor configuration;
data/EXAMPLES.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  |Name|Source & Description|Summary|
4
4
  |---|---|---|
5
+ |**conditional**|[examples/conditional.rb](./examples/conditional.rb)<br/>[examples/conditional.md](./examples/conditional.md)|Prints a sequence only when the writer supports colour|
5
6
  |**print_status**|[examples/print_status.rb](./examples/print_status.rb)<br/>[examples/print_status.md](./examples/print_status.md)|Prints sample status words with **woad** SGR colour constants|
6
7
 
7
8
 
data/NEWS.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # woad.Ruby - News <!-- omit in toc -->
2
2
 
3
- | Date | News Item |
4
- | ---------------- | -------------------------------------------------------------------------------------- |
5
- | 30th August 2026 | [**woad.Ruby** 0.0.4](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.4) |
6
- | 28th August 2026 | [**woad.Ruby** 0.0.3](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.3) |
7
- | 20th August 2026 | [**woad.Ruby** 0.0.2](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.2) |
8
- | 15th August 2026 | [**woad.Ruby** 0.0.1](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.1) |
9
- | 15th August 2026 | [**woad.Ruby** 0.0.0](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.0) |
3
+
4
+ | Date | News Item | Details |
5
+ | ------------------- | ------------------------- | -------------------------------------------------------------------- |
6
+ | 16th November 2026 | [**woad.Ruby** 0.1.0-alpha1](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.1.0-alpha1) | portable colour policy; `BOLD` and `BLINKING`; conditional `seq_for` |
7
+ | 30th August 2026 | [**woad.Ruby** 0.0.4](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.4) | shared **.vimrc**; dropped an obsolete CI branch |
8
+ | 28th August 2026 | [**woad.Ruby** 0.0.3](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.3) | Actions checkout **v7**; Ruby 2.0+ requirement |
9
+ | 20th August 2026 | [**woad.Ruby** 0.0.2](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.2) | **examples/print_status**; packaging and **README.md** |
10
+ | 15th August 2026 | [**woad.Ruby** 0.0.1](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.1) | fixed SGR colour and reset sequences |
11
+ | 15th August 2026 | [**woad.Ruby** 0.0.0](https://github.com/synesissoftware/woad.Ruby/releases/tag/0.0.0) | initial scaffolding; Ruby 2.0+ |
10
12
 
11
13
 
12
14
  <!-- ########################### end of file ########################### -->
data/README.md CHANGED
@@ -12,8 +12,10 @@ Minimal ANSI terminal colour codes, for Ruby
12
12
  ## Table of Contents <!-- omit in toc -->
13
13
 
14
14
  - [Introduction](#introduction)
15
+ - [Developer Experience](#developer-experience)
15
16
  - [Installation](#installation)
16
17
  - [Components](#components)
18
+ - [Environment](#environment)
17
19
  - [Examples](#examples)
18
20
  - [Project Information](#project-information)
19
21
  - [Where to get help](#where-to-get-help)
@@ -38,6 +40,30 @@ Minimal ANSI terminal colour codes, for Ruby
38
40
  It has **no dependencies** on any other non-standard library.
39
41
 
40
42
 
43
+ ## Developer Experience
44
+
45
+ **Ruby 2.0+** is already enough. There are no gems to install beyond **woad** itself.
46
+
47
+ ```
48
+ gem install woad
49
+ ```
50
+
51
+ The smallest useful call prints a sequence only when the writer supports colour:
52
+
53
+ ```Ruby
54
+ require 'woad'
55
+
56
+ green = Woad.seq_for($stdout, Woad::FG_GREEN)
57
+ reset = Woad.seq_for($stdout, Woad::RESET)
58
+
59
+ puts "#{green}ok#{reset}"
60
+ ```
61
+
62
+ That is [examples/conditional.rb](./examples/conditional.rb). Fixed sequences (`BOLD`, `BLINKING`, `FG_*`, `BG_*`) always contain the SGR bytes. `colour_policy`, `supports_colour`, and `seq_for` apply the cached environment policy. `refresh` drops that cache.
63
+
64
+ From a checkout, `bundle exec rake test` runs the unit and component suites. Installation detail is in [INSTALL.md](./INSTALL.md).
65
+
66
+
41
67
  ## Installation
42
68
 
43
69
  Install via **gem** as in:
@@ -59,7 +85,25 @@ puts "#{Woad::FG_GREEN}ok#{Woad::RESET}"
59
85
 
60
86
  ## Components
61
87
 
62
- **woad.Ruby** ships SGR string constants (`RESET`, `FG_*`, `BG_*`, including bright variants). TTY/stream gating and Windows virtual-terminal opt-in are not implemented yet.
88
+ **woad.Ruby** ships fixed SGR sequences (`BLINKING`, `BOLD`, `RESET`, `FG_*`, `BG_*`, including bright variants) and the portable colour policy: `colour_policy`, `refresh`, `supports_colour`, and `seq_for`. On Unix a terminal supports colour when the policy is automatic and `TERM` is not `dumb`. Windows virtual-terminal mode and mintty PTY recognition are not implemented.
89
+
90
+
91
+ ## Environment
92
+
93
+ `Woad.colour_policy` returns `COLOUR_POLICY_AUTO` (0), `COLOUR_POLICY_FORCE` (1), or `COLOUR_POLICY_INHIBIT` (2). The result is cached until `Woad.refresh` is invoked.
94
+
95
+ A variable that is unset or empty is absent. Values are not trimmed. The first present variable in this order wins:
96
+
97
+ 1. Non-empty `WOAD_COLOUR`, otherwise non-empty `WOAD_COLOR`. Compared case-insensitively. Truey forces: `1`, `always`, `force`, `on`, `true`, `yes`. Falsey inhibits: `0`, `false`, `never`, `no`, `none`, `off`. Anything else, including `auto`, selects auto and does not fall through;
98
+ 2. Non-empty `NO_COLOR` inhibits, whatever its text is;
99
+ 3. Non-empty `CLICOLOR_FORCE` other than the exact bytes `0` forces. Exact `0` does not force and does not inhibit;
100
+ 4. Non-empty `FORCE_COLOR` other than exact `0` or `false` (any capitalisation) forces. Exact `0` and `false` do not force and do not inhibit;
101
+ 5. `CLICOLOR` equal to the exact bytes `0` inhibits. Any other value is ignored;
102
+ 6. Otherwise auto.
103
+
104
+ `TERM` equal to `dumb`, in any capitalisation, is cached beside the policy. It is not a policy value. It suppresses colour only when the policy is auto. `force` ignores the terminal and `TERM`. `inhibit` never emits colour.
105
+
106
+ On Unix, auto plus a terminal plus a `TERM` that is not `dumb` means colour. There is no console probe. `WOAD_SET_CONSOLE_MODE` is not read in this alpha.
63
107
 
64
108
 
65
109
  ## Examples
@@ -90,7 +134,7 @@ Libraries upon which **woad.Ruby** depends:
90
134
 
91
135
  ##### Runtime Dependencies (aka "Normal Dependencies")
92
136
 
93
- * \<none>;
137
+ * \<none>. Windows virtual-terminal mode and mintty PTY recognition are not implemented, so this gem does not bind a console API;
94
138
 
95
139
 
96
140
  ##### Development Dependencies
data/TODO.md CHANGED
@@ -4,8 +4,9 @@
4
4
  ## Functional improvements
5
5
 
6
6
  * [x] ~~~SGR colour and reset codes~~~ ✅;
7
- * [ ] TTY-conditional colour codes (process and per-stream);
8
- * [ ] Windows virtual-terminal gating (OS build + `GetConsoleMode`);
7
+ * [x] ~~~portable colour policy (`colour_policy`, `refresh`, `supports_colour`, `seq_for`), including `BOLD` and `BLINKING`~~~ - ✅;
8
+ * [ ] Windows virtual-terminal gating (OS build + `GetConsoleMode` + `WOAD_SET_CONSOLE_MODE`);
9
+ * [ ] mintty / MSYS2 / Cygwin PTY recognition;
9
10
 
10
11
 
11
12
  ## Performance improvements
@@ -0,0 +1,37 @@
1
+ # woad.Ruby - Example - **conditional**
2
+
3
+
4
+ ## Summary
5
+
6
+ Prints a status word with an SGR sequence only when standard output supports colour.
7
+
8
+
9
+ ## Source
10
+
11
+ ```ruby
12
+
13
+ #! /usr/bin/env ruby
14
+
15
+ # examples/conditional.rb
16
+
17
+ require 'woad'
18
+
19
+ green = Woad.seq_for($stdout, Woad::FG_GREEN)
20
+ reset = Woad.seq_for($stdout, Woad::RESET)
21
+
22
+ puts "#{green}ok#{reset}"
23
+ ```
24
+
25
+ `seq_for` returns the sequence when the writer supports colour, and `""` when it does not. A pipe, `TERM=dumb` under the automatic policy, and an inhibit policy all print `ok` with no escape sequences.
26
+
27
+
28
+ ## Usage
29
+
30
+ When run, this produces the following output (colours omitted here):
31
+
32
+ ```
33
+ ok
34
+ ```
35
+
36
+
37
+ <!-- ########################### end of file ########################### -->
@@ -0,0 +1,12 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ # examples/conditional.rb
4
+
5
+
6
+ require 'woad'
7
+
8
+
9
+ green = Woad.seq_for($stdout, Woad::FG_GREEN)
10
+ reset = Woad.seq_for($stdout, Woad::RESET)
11
+
12
+ puts "#{green}ok#{reset}"
File without changes
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+ # ######################################################################## #
3
+ # File: woad/sequences.rb
4
+ #
5
+ # Purpose: Fixed SGR sequences for woad.Ruby
6
+ #
7
+ # Created: 16th November 2026
8
+ # Updated: 16th November 2026
9
+ #
10
+ # Home: https://github.com/synesissoftware/woad.Ruby
11
+ #
12
+ # Author: Matthew Wilson
13
+ #
14
+ # Copyright (c) 2026, Matthew Wilson and Synesis Information Systems
15
+ # All rights reserved.
16
+ #
17
+ # Redistribution and use in source and binary forms, with or without
18
+ # modification, are permitted provided that the following conditions are
19
+ # met:
20
+ #
21
+ # * Redistributions of source code must retain the above copyright
22
+ # notice, this list of conditions and the following disclaimer.
23
+ #
24
+ # * Redistributions in binary form must reproduce the above copyright
25
+ # notice, this list of conditions and the following disclaimer in the
26
+ # documentation and/or other materials provided with the distribution.
27
+ #
28
+ # * Neither the names of the copyright holder nor the names of its
29
+ # contributors may be used to endorse or promote products derived from
30
+ # this software without specific prior written permission.
31
+ #
32
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
33
+ # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
34
+ # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
35
+ # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
36
+ # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
37
+ # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
38
+ # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
39
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
40
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
41
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
42
+ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43
+ #
44
+ # ######################################################################## #
45
+
46
+
47
+ =begin
48
+ =end
49
+
50
+ module Woad
51
+
52
+ # Fixed SGR sequences. They always contain the escape bytes.
53
+ module Sequences
54
+
55
+ # Attributes
56
+
57
+ BLINKING = "\e[5m"
58
+ BOLD = "\e[1m"
59
+ RESET = "\e[0m"
60
+
61
+ # Foreground (standard)
62
+
63
+ FG_BLACK = "\e[30m"
64
+ FG_RED = "\e[31m"
65
+ FG_GREEN = "\e[32m"
66
+ FG_YELLOW = "\e[33m"
67
+ FG_BLUE = "\e[34m"
68
+ FG_MAGENTA = "\e[35m"
69
+ FG_CYAN = "\e[36m"
70
+ FG_WHITE = "\e[37m"
71
+
72
+ # Foreground (bright)
73
+
74
+ FG_BRIGHT_BLACK = "\e[90m"
75
+ FG_BRIGHT_RED = "\e[91m"
76
+ FG_BRIGHT_GREEN = "\e[92m"
77
+ FG_BRIGHT_YELLOW = "\e[93m"
78
+ FG_BRIGHT_BLUE = "\e[94m"
79
+ FG_BRIGHT_MAGENTA = "\e[95m"
80
+ FG_BRIGHT_CYAN = "\e[96m"
81
+ FG_BRIGHT_WHITE = "\e[97m"
82
+
83
+ # Background (standard)
84
+
85
+ BG_BLACK = "\e[40m"
86
+ BG_RED = "\e[41m"
87
+ BG_GREEN = "\e[42m"
88
+ BG_YELLOW = "\e[43m"
89
+ BG_BLUE = "\e[44m"
90
+ BG_MAGENTA = "\e[45m"
91
+ BG_CYAN = "\e[46m"
92
+ BG_WHITE = "\e[47m"
93
+
94
+ # Background (bright)
95
+
96
+ BG_BRIGHT_BLACK = "\e[100m"
97
+ BG_BRIGHT_RED = "\e[101m"
98
+ BG_BRIGHT_GREEN = "\e[102m"
99
+ BG_BRIGHT_YELLOW = "\e[103m"
100
+ BG_BRIGHT_BLUE = "\e[104m"
101
+ BG_BRIGHT_MAGENTA = "\e[105m"
102
+ BG_BRIGHT_CYAN = "\e[106m"
103
+ BG_BRIGHT_WHITE = "\e[107m"
104
+ end # module Sequences
105
+ end # module Woad
106
+
107
+
108
+ # ############################## end of file ############################# #
data/lib/woad/version.rb CHANGED
@@ -5,7 +5,7 @@
5
5
  # Purpose: Version for woad.Ruby library
6
6
  #
7
7
  # Created: 15th August 2026
8
- # Updated: 30th August 2026
8
+ # Updated: 16th November 2026
9
9
  #
10
10
  # Home: https://github.com/synesissoftware/woad.Ruby
11
11
  #
@@ -50,7 +50,7 @@
50
50
  module Woad
51
51
 
52
52
  # Current version of the woad.Ruby library
53
- VERSION = '0.0.4'
53
+ VERSION = '0.1.0-alpha1'
54
54
 
55
55
  private
56
56
  # @!visibility private
data/lib/woad.rb CHANGED
@@ -5,7 +5,7 @@
5
5
  # Purpose: Primary require for woad.Ruby library
6
6
  #
7
7
  # Created: 15th August 2026
8
- # Updated: 19th August 2026
8
+ # Updated: 16th November 2026
9
9
  #
10
10
  # Home: https://github.com/synesissoftware/woad.Ruby
11
11
  #
@@ -47,56 +47,176 @@
47
47
  =begin
48
48
  =end
49
49
 
50
+ require 'woad/sequences'
51
+
52
+
50
53
  ## Root module for *woad*
51
54
  module Woad
52
55
 
53
- # Reset
54
-
55
- RESET = "\e[0m"
56
-
57
- # Foreground (standard)
58
-
59
- FG_BLACK = "\e[30m"
60
- FG_RED = "\e[31m"
61
- FG_GREEN = "\e[32m"
62
- FG_YELLOW = "\e[33m"
63
- FG_BLUE = "\e[34m"
64
- FG_MAGENTA = "\e[35m"
65
- FG_CYAN = "\e[36m"
66
- FG_WHITE = "\e[37m"
67
-
68
- # Foreground (bright)
69
-
70
- FG_BRIGHT_BLACK = "\e[90m"
71
- FG_BRIGHT_RED = "\e[91m"
72
- FG_BRIGHT_GREEN = "\e[92m"
73
- FG_BRIGHT_YELLOW = "\e[93m"
74
- FG_BRIGHT_BLUE = "\e[94m"
75
- FG_BRIGHT_MAGENTA = "\e[95m"
76
- FG_BRIGHT_CYAN = "\e[96m"
77
- FG_BRIGHT_WHITE = "\e[97m"
78
-
79
- # Background (standard)
80
-
81
- BG_BLACK = "\e[40m"
82
- BG_RED = "\e[41m"
83
- BG_GREEN = "\e[42m"
84
- BG_YELLOW = "\e[43m"
85
- BG_BLUE = "\e[44m"
86
- BG_MAGENTA = "\e[45m"
87
- BG_CYAN = "\e[46m"
88
- BG_WHITE = "\e[47m"
89
-
90
- # Background (bright)
91
-
92
- BG_BRIGHT_BLACK = "\e[100m"
93
- BG_BRIGHT_RED = "\e[101m"
94
- BG_BRIGHT_GREEN = "\e[102m"
95
- BG_BRIGHT_YELLOW = "\e[103m"
96
- BG_BRIGHT_BLUE = "\e[104m"
97
- BG_BRIGHT_MAGENTA = "\e[105m"
98
- BG_BRIGHT_CYAN = "\e[106m"
99
- BG_BRIGHT_WHITE = "\e[107m"
56
+ include Sequences
57
+
58
+ # Colour policy (cached until refresh)
59
+
60
+ COLOUR_POLICY_AUTO = 0
61
+ COLOUR_POLICY_FORCE = 1
62
+ COLOUR_POLICY_INHIBIT = 2
63
+
64
+ # @!visibility private
65
+ FALSEY_ = [ # :nodoc:
66
+ '0',
67
+ 'false',
68
+ 'never',
69
+ 'no',
70
+ 'none',
71
+ 'off',
72
+ ].freeze
73
+ # @!visibility private
74
+ TRUEY_ = [ # :nodoc:
75
+ '1',
76
+ 'always',
77
+ 'force',
78
+ 'on',
79
+ 'true',
80
+ 'yes',
81
+ ].freeze
82
+ private_constant :FALSEY_
83
+ private_constant :TRUEY_
84
+
85
+ # Returns the cached colour policy: 0 auto, 1 force, 2 inhibit.
86
+ def self.colour_policy
87
+
88
+ ensure_environment_
89
+
90
+ @policy
91
+ end
92
+
93
+ # Drops the cached policy and the cached TERM=dumb flag.
94
+ def self.refresh
95
+
96
+ @environment_determined = false
97
+
98
+ nil
99
+ end
100
+
101
+ # True when colour should be emitted for this writer.
102
+ def self.supports_colour(io)
103
+
104
+ ensure_environment_
105
+
106
+ case @policy
107
+ when COLOUR_POLICY_FORCE
108
+
109
+ return true
110
+ when COLOUR_POLICY_INHIBIT
111
+
112
+ return false
113
+ end
114
+
115
+ return false unless attached_(io)
116
+ return false if @term_is_dumb
117
+
118
+ true
119
+ end
120
+
121
+ # The sequence when the writer supports colour; otherwise "".
122
+ def self.seq_for(io, seq)
123
+
124
+ return '' if seq.nil?
125
+ return '' unless supports_colour(io)
126
+
127
+ seq
128
+ end
129
+
130
+ class << self
131
+
132
+ private
133
+
134
+ # True when the writer reports a terminal. Nil is not attached.
135
+ def attached_(io)
136
+
137
+ return false if io.nil?
138
+ return false unless io.respond_to?(:isatty)
139
+
140
+ io.isatty ? true : false
141
+ end
142
+
143
+ # Fills the policy cache and the TERM=dumb flag from the environment.
144
+ def ensure_environment_
145
+
146
+ return if @environment_determined
147
+
148
+ @policy = policy_from_environment_
149
+ @term_is_dumb = term_is_dumb_
150
+ @environment_determined = true
151
+ end
152
+
153
+ # The environment value, or nil when the variable is unset or empty.
154
+ def env_nonempty_(name)
155
+
156
+ value = ENV[name]
157
+
158
+ return nil if value.nil? || value.empty?
159
+
160
+ value
161
+ end
162
+
163
+ # True when the value equals one of the tokens, ignoring case.
164
+ def listed_?(value, tokens)
165
+
166
+ tokens.any? { |token| value.casecmp(token) == 0 }
167
+ end
168
+
169
+ # The colour policy from the environment. The first match wins.
170
+ def policy_from_environment_
171
+
172
+ value = env_nonempty_('WOAD_COLOUR')
173
+ value = env_nonempty_('WOAD_COLOR') if value.nil?
174
+
175
+ unless value.nil?
176
+
177
+ return COLOUR_POLICY_FORCE if listed_?(value, TRUEY_)
178
+ return COLOUR_POLICY_INHIBIT if listed_?(value, FALSEY_)
179
+
180
+ return COLOUR_POLICY_AUTO
181
+ end
182
+
183
+ return COLOUR_POLICY_INHIBIT unless env_nonempty_('NO_COLOR').nil?
184
+
185
+ value = env_nonempty_('CLICOLOR_FORCE')
186
+
187
+ unless value.nil?
188
+
189
+ return COLOUR_POLICY_FORCE if value != '0'
190
+ end
191
+
192
+ value = env_nonempty_('FORCE_COLOR')
193
+
194
+ unless value.nil?
195
+
196
+ if value != '0' && value.casecmp('false') != 0
197
+
198
+ return COLOUR_POLICY_FORCE
199
+ end
200
+ end
201
+
202
+ value = env_nonempty_('CLICOLOR')
203
+
204
+ unless value.nil?
205
+
206
+ return COLOUR_POLICY_INHIBIT if value == '0'
207
+ end
208
+
209
+ COLOUR_POLICY_AUTO
210
+ end
211
+
212
+ # True when TERM is "dumb" in any capitalisation.
213
+ def term_is_dumb_
214
+
215
+ value = env_nonempty_('TERM')
216
+
217
+ !value.nil? && value.casecmp('dumb') == 0
218
+ end
219
+ end
100
220
  end # module Woad
101
221
 
102
222
 
@@ -0,0 +1,317 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), '../../lib')
4
+
5
+
6
+ require 'woad'
7
+
8
+ require 'test/unit'
9
+
10
+
11
+ class Test_colour_policy < Test::Unit::TestCase
12
+
13
+ ENV_NAMES_ = [
14
+ 'CLICOLOR',
15
+ 'CLICOLOR_FORCE',
16
+ 'FORCE_COLOR',
17
+ 'NO_COLOR',
18
+ 'TERM',
19
+ 'WOAD_COLOR',
20
+ 'WOAD_COLOUR',
21
+ ].freeze
22
+
23
+ class Writer_
24
+
25
+ def initialize(attached)
26
+
27
+ @attached = attached
28
+ end
29
+
30
+ def isatty
31
+
32
+ @attached
33
+ end
34
+ end
35
+
36
+ TERMINAL_ = Writer_.new(true)
37
+ DETACHED_ = Writer_.new(false)
38
+
39
+ def observe(assignments, attached)
40
+
41
+ saved = {}
42
+
43
+ ENV_NAMES_.each do |name|
44
+
45
+ saved[name] = ENV.key?(name) ? ENV[name] : nil
46
+ ENV.delete(name)
47
+ end
48
+
49
+ assignments.each do |name, value|
50
+
51
+ ENV[name] = value
52
+ end
53
+
54
+ Woad.refresh
55
+
56
+ io = attached ? TERMINAL_ : DETACHED_
57
+
58
+ [
59
+ Woad.colour_policy,
60
+ Woad.supports_colour(io),
61
+ Woad.seq_for(io, Woad::FG_RED),
62
+ Woad.seq_for(io, nil),
63
+ ]
64
+ ensure
65
+
66
+ ENV_NAMES_.each do |name|
67
+
68
+ if saved[name].nil?
69
+
70
+ ENV.delete(name)
71
+ else
72
+
73
+ ENV[name] = saved[name]
74
+ end
75
+ end
76
+
77
+ Woad.refresh
78
+ end
79
+
80
+ def test_row_1_none_terminal
81
+
82
+ policy, colour, seq, empty = observe({}, true)
83
+
84
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
85
+ assert_equal true, colour
86
+ assert_equal Woad::FG_RED, seq
87
+ assert_equal '', empty
88
+ end
89
+
90
+ def test_row_2_none_detached
91
+
92
+ policy, colour, seq, empty = observe({}, false)
93
+
94
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
95
+ assert_equal false, colour
96
+ assert_equal '', seq
97
+ assert_equal '', empty
98
+ end
99
+
100
+ def test_row_3_term_dumb
101
+
102
+ policy, colour, = observe({ 'TERM' => 'dumb' }, true)
103
+
104
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
105
+ assert_equal false, colour
106
+ end
107
+
108
+ def test_row_4_term_DUMB
109
+
110
+ policy, colour, = observe({ 'TERM' => 'DUMB' }, true)
111
+
112
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
113
+ assert_equal false, colour
114
+ end
115
+
116
+ def test_row_5_woad_colour_1
117
+
118
+ policy, colour, seq, = observe({ 'WOAD_COLOUR' => '1' }, false)
119
+
120
+ assert_equal Woad::COLOUR_POLICY_FORCE, policy
121
+ assert_equal true, colour
122
+ assert_equal Woad::FG_RED, seq
123
+ end
124
+
125
+ def test_row_6_woad_color_always
126
+
127
+ policy, colour, = observe({ 'WOAD_COLOR' => 'always' }, false)
128
+
129
+ assert_equal Woad::COLOUR_POLICY_FORCE, policy
130
+ assert_equal true, colour
131
+ end
132
+
133
+ def test_row_7_woad_colour_never
134
+
135
+ policy, colour, = observe({ 'WOAD_COLOUR' => 'never' }, true)
136
+
137
+ assert_equal Woad::COLOUR_POLICY_INHIBIT, policy
138
+ assert_equal false, colour
139
+ end
140
+
141
+ def test_row_8_woad_colour_auto_terminal
142
+
143
+ policy, colour, = observe({ 'WOAD_COLOUR' => 'auto' }, true)
144
+
145
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
146
+ assert_equal true, colour
147
+ end
148
+
149
+ def test_row_9_woad_colour_auto_detached
150
+
151
+ policy, colour, = observe({ 'WOAD_COLOUR' => 'auto' }, false)
152
+
153
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
154
+ assert_equal false, colour
155
+ end
156
+
157
+ def test_row_10_woad_colour_purple
158
+
159
+ policy, colour, = observe({ 'WOAD_COLOUR' => 'purple' }, true)
160
+
161
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
162
+ assert_equal true, colour
163
+ end
164
+
165
+ def test_row_11_no_color_outranks_force
166
+
167
+ policy, colour, = observe({
168
+ 'CLICOLOR_FORCE' => '1',
169
+ 'NO_COLOR' => '1',
170
+ }, true)
171
+
172
+ assert_equal Woad::COLOUR_POLICY_INHIBIT, policy
173
+ assert_equal false, colour
174
+ end
175
+
176
+ def test_row_12_clicolor_force
177
+
178
+ policy, colour, = observe({ 'CLICOLOR_FORCE' => '1' }, false)
179
+
180
+ assert_equal Woad::COLOUR_POLICY_FORCE, policy
181
+ assert_equal true, colour
182
+ end
183
+
184
+ def test_row_13_clicolor_force_0_then_clicolor_0
185
+
186
+ policy, colour, = observe({
187
+ 'CLICOLOR' => '0',
188
+ 'CLICOLOR_FORCE' => '0',
189
+ }, true)
190
+
191
+ assert_equal Woad::COLOUR_POLICY_INHIBIT, policy
192
+ assert_equal false, colour
193
+ end
194
+
195
+ def test_row_14_force_color
196
+
197
+ policy, colour, = observe({ 'FORCE_COLOR' => '1' }, false)
198
+
199
+ assert_equal Woad::COLOUR_POLICY_FORCE, policy
200
+ assert_equal true, colour
201
+ end
202
+
203
+ def test_row_15_force_color_false_then_clicolor_0
204
+
205
+ policy, colour, = observe({
206
+ 'CLICOLOR' => '0',
207
+ 'FORCE_COLOR' => 'false',
208
+ }, true)
209
+
210
+ assert_equal Woad::COLOUR_POLICY_INHIBIT, policy
211
+ assert_equal false, colour
212
+ end
213
+
214
+ def test_row_16_force_color_FALSE_detached
215
+
216
+ policy, colour, = observe({ 'FORCE_COLOR' => 'FALSE' }, false)
217
+
218
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
219
+ assert_equal false, colour
220
+ end
221
+
222
+ def test_row_17_force_color_0_terminal
223
+
224
+ policy, colour, = observe({ 'FORCE_COLOR' => '0' }, true)
225
+
226
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
227
+ assert_equal true, colour
228
+ end
229
+
230
+ def test_row_18_clicolor_0
231
+
232
+ policy, colour, = observe({ 'CLICOLOR' => '0' }, true)
233
+
234
+ assert_equal Woad::COLOUR_POLICY_INHIBIT, policy
235
+ assert_equal false, colour
236
+ end
237
+
238
+ def test_row_19_clicolor_1
239
+
240
+ policy, colour, = observe({ 'CLICOLOR' => '1' }, true)
241
+
242
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
243
+ assert_equal true, colour
244
+ end
245
+
246
+ def test_row_20_empty_woad_colour_is_absent
247
+
248
+ policy, colour, = observe({
249
+ 'NO_COLOR' => '1',
250
+ 'WOAD_COLOUR' => '',
251
+ }, true)
252
+
253
+ assert_equal Woad::COLOUR_POLICY_INHIBIT, policy
254
+ assert_equal false, colour
255
+ end
256
+
257
+ def test_row_21_empty_no_color_is_absent
258
+
259
+ policy, colour, = observe({ 'NO_COLOR' => '' }, true)
260
+
261
+ assert_equal Woad::COLOUR_POLICY_AUTO, policy
262
+ assert_equal true, colour
263
+ end
264
+
265
+ def test_row_22_refresh_drops_the_cache
266
+
267
+ saved = {}
268
+
269
+ ENV_NAMES_.each do |name|
270
+
271
+ saved[name] = ENV.key?(name) ? ENV[name] : nil
272
+ ENV.delete(name)
273
+ end
274
+
275
+ ENV['WOAD_COLOUR'] = '1'
276
+ Woad.refresh
277
+
278
+ assert_equal Woad::COLOUR_POLICY_FORCE, Woad.colour_policy
279
+ assert_equal true, Woad.supports_colour(TERMINAL_)
280
+
281
+ ENV.delete('WOAD_COLOUR')
282
+ ENV['NO_COLOR'] = '1'
283
+
284
+ assert_equal Woad::COLOUR_POLICY_FORCE, Woad.colour_policy
285
+ assert_equal true, Woad.supports_colour(TERMINAL_)
286
+
287
+ Woad.refresh
288
+
289
+ assert_equal Woad::COLOUR_POLICY_INHIBIT, Woad.colour_policy
290
+ assert_equal false, Woad.supports_colour(TERMINAL_)
291
+ ensure
292
+
293
+ ENV_NAMES_.each do |name|
294
+
295
+ if saved[name].nil?
296
+
297
+ ENV.delete(name)
298
+ else
299
+
300
+ ENV[name] = saved[name]
301
+ end
302
+ end
303
+
304
+ Woad.refresh
305
+ end
306
+
307
+ def test_force_ignores_term_dumb
308
+
309
+ policy, colour, = observe({
310
+ 'TERM' => 'dumb',
311
+ 'WOAD_COLOUR' => 'yes',
312
+ }, false)
313
+
314
+ assert_equal Woad::COLOUR_POLICY_FORCE, policy
315
+ assert_equal true, colour
316
+ end
317
+ end
@@ -10,6 +10,16 @@ require 'test/unit'
10
10
 
11
11
  class Test_codes < Test::Unit::TestCase
12
12
 
13
+ def test_BLINKING
14
+
15
+ assert_equal "\e[5m", Woad::BLINKING
16
+ end
17
+
18
+ def test_BOLD
19
+
20
+ assert_equal "\e[1m", Woad::BOLD
21
+ end
22
+
13
23
  def test_RESET
14
24
 
15
25
  assert_equal "\e[0m", Woad::RESET
@@ -28,6 +38,8 @@ class Test_codes < Test::Unit::TestCase
28
38
  def test_codes_are_csi_sgr
29
39
 
30
40
  [
41
+ Woad::BLINKING,
42
+ Woad::BOLD,
31
43
  Woad::RESET,
32
44
  Woad::FG_BLACK,
33
45
  Woad::FG_BRIGHT_WHITE,
@@ -32,6 +32,9 @@ class Test_version < Test::Unit::TestCase
32
32
 
33
33
  def test_VERSION_has_consistent_format
34
34
 
35
- assert_equal Woad::VERSION.split('.')[0..2].join('.'), "#{Woad::VERSION_MAJOR}.#{Woad::VERSION_MINOR}.#{Woad::VERSION_REVISION}"
35
+ core = Woad::VERSION.split('-', 2)[0]
36
+
37
+ assert_equal '0.1.0-alpha1', Woad::VERSION
38
+ assert_equal "#{Woad::VERSION_MAJOR}.#{Woad::VERSION_MINOR}.#{Woad::VERSION_REVISION}", core
36
39
  end
37
40
  end
data/test/unit/ts_all.rb CHANGED
File without changes
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: woad
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0.pre.alpha1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Wilson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-30 00:00:00.000000000 Z
11
+ date: 2026-11-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Minimal ANSI terminal colour codes, for Ruby.
@@ -32,10 +32,14 @@ files:
32
32
  - Rakefile
33
33
  - SECURITY.md
34
34
  - TODO.md
35
+ - examples/conditional.md
36
+ - examples/conditional.rb
35
37
  - examples/print_status.md
36
38
  - examples/print_status.rb
37
39
  - lib/woad.rb
40
+ - lib/woad/sequences.rb
38
41
  - lib/woad/version.rb
42
+ - test/component/tc_colour_policy.rb
39
43
  - test/unit/tc_codes.rb
40
44
  - test/unit/tc_version.rb
41
45
  - test/unit/ts_all.rb
@@ -61,9 +65,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
61
65
  version: '5'
62
66
  required_rubygems_version: !ruby/object:Gem::Requirement
63
67
  requirements:
64
- - - ">="
68
+ - - ">"
65
69
  - !ruby/object:Gem::Version
66
- version: '0'
70
+ version: 1.3.1
67
71
  requirements: []
68
72
  rubyforge_project:
69
73
  rubygems_version: 2.7.6.3