na 1.2.79 → 1.2.81

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.
data/lib/na/colors.rb CHANGED
@@ -5,7 +5,7 @@ module NA
5
5
  # Terminal output color functions.
6
6
  module Color
7
7
  # Regexp to match excape sequences
8
- ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/
8
+ ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/.freeze
9
9
 
10
10
  # All available color names. Available as methods and string extensions.
11
11
  #
@@ -99,35 +99,33 @@ module NA
99
99
 
100
100
  # Template coloring
101
101
  class ::String
102
- ##
103
- ## Extract the longest valid %color name from a string.
104
- ##
105
- ## Allows %colors to bleed into other text and still
106
- ## be recognized, e.g. %greensomething still finds
107
- ## %green.
108
- ##
109
- ## @return [String] a valid color name
110
- ##
102
+ # Extract the longest valid %color name from a string.
103
+ #
104
+ # Allows %colors to bleed into other text and still
105
+ # be recognized, e.g. %greensomething still finds
106
+ # %green.
107
+ #
108
+ # @return [String] a valid color name
111
109
  def validate_color
112
110
  valid_color = nil
113
111
  compiled = ''
114
- normalize_color.split('').each do |char|
112
+ normalize_color.chars.each do |char|
115
113
  compiled += char
116
- valid_color = compiled if Color.attributes.include?(compiled.to_sym) || compiled =~ /^([fb]g?)?#([a-f0-9]{6})$/i
114
+ if Color.attributes.include?(compiled.to_sym) || compiled =~ /^([fb]g?)?#([a-f0-9]{6})$/i
115
+ valid_color = compiled
116
+ end
117
117
  end
118
118
 
119
119
  valid_color
120
120
  end
121
121
 
122
- ##
123
- ## Normalize a color name, removing underscores,
124
- ## replacing "bright" with "bold", and converting
125
- ## bgbold to boldbg
126
- ##
127
- ## @return [String] Normalized color name
128
- ##
122
+ # Normalize a color name, removing underscores,
123
+ # replacing "bright" with "bold", and converting
124
+ # bgbold to boldbg
125
+ #
126
+ # @return [String] Normalized color name
129
127
  def normalize_color
130
- gsub(/_/, '').sub(/bright/i, 'bold').sub(/bgbold/, 'boldbg')
128
+ gsub('_', '').sub(/bright/i, 'bold').sub('bgbold', 'boldbg')
131
129
  end
132
130
 
133
131
  # Get the calculated ANSI color at the end of the
@@ -159,20 +157,20 @@ module NA
159
157
  rgbb = c
160
158
  end
161
159
  else
162
- c.split(/;/).each do |i|
160
+ c.split(';').each do |i|
163
161
  x = i.to_i
164
162
  if x <= 9
165
163
  em << x
166
- elsif x >= 30 && x <= 39
164
+ elsif x.between?(30, 39)
167
165
  rgbf = nil
168
166
  fg = x
169
- elsif x >= 40 && x <= 49
167
+ elsif x.between?(40, 49)
170
168
  rgbb = nil
171
169
  bg = x
172
- elsif x >= 90 && x <= 97
170
+ elsif x.between?(90, 97)
173
171
  rgbf = nil
174
172
  fg = x
175
- elsif x >= 100 && x <= 107
173
+ elsif x.between?(100, 107)
176
174
  rgbb = nil
177
175
  bg = x
178
176
  end
@@ -196,63 +194,86 @@ module NA
196
194
 
197
195
  attr_writer :coloring
198
196
 
199
- ##
200
- ## Enables colored output
201
- ##
202
- ## @example Turn color on or off based on TTY
203
- ## NA::Color.coloring = STDOUT.isatty
197
+ # Cache for compiled templates to avoid repeated regex processing
198
+ def template_cache
199
+ @template_cache ||= {}
200
+ end
201
+
202
+ def clear_template_cache
203
+ @template_cache = {}
204
+ end
205
+
206
+ # Pre-computed colors hash (expensive to create, so we cache it)
207
+ def colors_hash
208
+ @colors_hash ||= { w: white, k: black, g: green, l: blue,
209
+ y: yellow, c: cyan, m: magenta, r: red,
210
+ W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
211
+ Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
212
+ d: dark, b: bold, u: underline, i: italic, x: reset }
213
+ end
214
+
215
+ #
216
+ # Enables colored output
217
+ #
218
+ # @example Turn color on or off based on TTY
219
+ # NA::Color.coloring = STDOUT.isatty
204
220
  def coloring
205
221
  @coloring ||= true
206
222
  end
207
223
 
208
- ##
209
- ## Convert a template string to a colored string.
210
- ## Colors are specified with single letters inside
211
- ## curly braces. Uppercase changes background color.
212
- ##
213
- ## w: white, k: black, g: green, l: blue, y: yellow,
214
- ## c: cyan, m: magenta, r: red, b: bold, u: underline,
215
- ## i: italic, x: reset (remove background, color,
216
- ## emphasis)
217
- ##
218
- ## Also accepts {#RGB} and {#RRGGBB} strings. Put a b
219
- ## before the hash to make it a background color
220
- ##
221
- ## @example Convert a templated string
222
- ## Color.template('{Rwb}Warning:{x} {w}you look a
223
- ## little {g}ill{x}')
224
- ##
225
- ## @example Convert using RGB colors
226
- ## Color.template('{#f0a}This is an RGB color')
227
- ##
228
- ## @param input [String, Array] The template
229
- ## string. If this is an array, the
230
- ## elements will be joined with a
231
- ## space.
232
- ##
233
- ## @return [String] Colorized string
234
- ##
224
+ #
225
+ # Convert a template string to a colored string.
226
+ # Colors are specified with single letters inside
227
+ # curly braces. Uppercase changes background color.
228
+ #
229
+ # w: white, k: black, g: green, l: blue, y: yellow,
230
+ # c: cyan, m: magenta, r: red, b: bold, u: underline,
231
+ # i: italic, x: reset (remove background, color,
232
+ # emphasis)
233
+ #
234
+ # Also accepts {#RGB} and {#RRGGBB} strings. Put a b
235
+ # before the hash to make it a background color
236
+ #
237
+ # @example Convert a templated string
238
+ # Color.template('{Rwb}Warning:{x} {w}you look a
239
+ # little {g}ill{x}')
240
+ #
241
+ # @example Convert using RGB colors
242
+ # Color.template('{#f0a}This is an RGB color')
243
+ #
244
+ # @param input [String, Array] The template
245
+ # string. If this is an array, the
246
+ # elements will be joined with a
247
+ # space.
248
+ #
249
+ # @return [String] Colorized string
250
+ #
235
251
  def template(input)
236
252
  input = input.join(' ') if input.is_a? Array
237
253
  return input.gsub(/(?<!\\)\{#?(\w+)\}/i, '') unless NA::Color.coloring?
238
254
 
239
- input = input.gsub(/(?<!\\)\{((?:[fb]g?)?#[a-f0-9]{3,6})\}/i) do
240
- hex = Regexp.last_match(1)
241
- rgb(hex)
242
- end
255
+ # Check cache first
256
+ cache_key = input.hash
257
+ return template_cache[cache_key] if template_cache.key?(cache_key)
243
258
 
244
- fmt = input.gsub(/%/, '%%')
259
+ # Process hex colors first
260
+ processed_input = input.gsub(/(?<!\\)\{((?:[fb]g?)?#[a-f0-9]{3,6})\}/i) do
261
+ hex = Regexp.last_match(1)
262
+ rgb(hex)
263
+ end
264
+
265
+ # Convert to format string
266
+ fmt = processed_input.gsub('%', '%%')
245
267
  fmt = fmt.gsub(/(?<!\\)\{(\w+)\}/i) do
246
- Regexp.last_match(1).split('').map { |c| "%<#{c}>s" }.join('')
268
+ Regexp.last_match(1).chars.map { |c| "%<#{c}>s" }.join
247
269
  end
248
270
 
249
- colors = { w: white, k: black, g: green, l: blue,
250
- y: yellow, c: cyan, m: magenta, r: red,
251
- W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
252
- Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
253
- d: dark, b: bold, u: underline, i: italic, x: reset }
271
+ # Use pre-computed colors hash
272
+ result = format(fmt, colors_hash)
254
273
 
255
- format(fmt, colors)
274
+ # Cache the result
275
+ template_cache[cache_key] = result
276
+ result
256
277
  end
257
278
  end
258
279
 
@@ -260,18 +281,18 @@ module NA
260
281
  new_method = <<-EOSCRIPT
261
282
  # Color string as #{c}
262
283
  def #{c}(string = nil)
263
- result = ''
264
- result << "\e[#{v}m" if NA::Color.coloring?
284
+ result = ''.dup
285
+ result << "\e[#{v}m".dup if NA::Color.coloring?
265
286
  if block_given?
266
287
  result << yield
267
288
  elsif string.respond_to?(:to_str)
268
- result << string.to_str
289
+ result << string.to_str.dup
269
290
  elsif respond_to?(:to_str)
270
- result << to_str
291
+ result << to_str.dup
271
292
  else
272
293
  return result #only switch on
273
294
  end
274
- result << "\e[0m" if NA::Color.coloring?
295
+ result << "\e[0m".dup if NA::Color.coloring?
275
296
  result
276
297
  end
277
298
  EOSCRIPT
@@ -283,19 +304,19 @@ module NA
283
304
  # Accept brightwhite in addition to boldwhite
284
305
  new_method = <<-EOSCRIPT
285
306
  # color string as #{c}
286
- def #{c.to_s.sub(/bold/, 'bright')}(string = nil)
287
- result = ''
288
- result << "\e[#{v}m" if NA::Color.coloring?
307
+ def #{c.to_s.sub('bold', 'bright')}(string = nil)
308
+ result = ''.dup
309
+ result << "\e[#{v}m".dup if NA::Color.coloring?
289
310
  if block_given?
290
311
  result << yield
291
312
  elsif string.respond_to?(:to_str)
292
- result << string.to_str
313
+ result << string.to_str.dup
293
314
  elsif respond_to?(:to_str)
294
- result << to_str
315
+ result << to_str.dup
295
316
  else
296
317
  return result #only switch on
297
318
  end
298
- result << "\e[0m" if NA::Color.coloring?
319
+ result << "\e[0m".dup if NA::Color.coloring?
299
320
  result
300
321
  end
301
322
  EOSCRIPT
@@ -303,13 +324,13 @@ module NA
303
324
  module_eval(new_method)
304
325
  end
305
326
 
306
- ##
307
- ## Generate escape codes for hex colors
308
- ##
309
- ## @param hex [String] The hexadecimal color code
310
- ##
311
- ## @return [String] ANSI escape string
312
- ##
327
+ #
328
+ # Generate escape codes for hex colors
329
+ #
330
+ # @param hex [String] The hexadecimal color code
331
+ #
332
+ # @return [String] ANSI escape string
333
+ #
313
334
  def rgb(hex)
314
335
  is_bg = hex.match(/^bg?#/) ? true : false
315
336
  hex_string = hex.sub(/^([fb]g?)?#/, '')
@@ -322,13 +343,12 @@ module NA
322
343
  t << parts[e]
323
344
  t << parts[e]
324
345
  end
325
- hex_string = t.join('')
346
+ hex_string = t.join
326
347
  end
327
348
 
328
349
  parts = hex_string.match(/(?<r>..)(?<g>..)(?<b>..)/)
329
- t = []
330
- %w[r g b].each do |e|
331
- t << parts[e].hex
350
+ t = %w[r g b].map do |e|
351
+ parts[e].hex
332
352
  end
333
353
 
334
354
  "\e[#{is_bg ? '48' : '38'};2;#{t.join(';')}m"
@@ -336,7 +356,7 @@ module NA
336
356
 
337
357
  # Regular expression that is used to scan for ANSI-sequences while
338
358
  # uncoloring strings.
339
- COLORED_REGEXP = /\e\[(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+m/
359
+ COLORED_REGEXP = /\e\[(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+m/.freeze
340
360
 
341
361
  # Returns an uncolored version of the string, that is all
342
362
  # ANSI-sequences are stripped from the string.
@@ -356,6 +376,7 @@ module NA
356
376
  def attributes
357
377
  ATTRIBUTE_NAMES
358
378
  end
379
+
359
380
  extend self
360
381
  end
361
382
  end
data/lib/na/editor.rb CHANGED
@@ -1,16 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
1
5
  module NA
2
6
  module Editor
3
7
  class << self
4
8
  def default_editor(prefer_git_editor: true)
5
- if prefer_git_editor
6
- editor ||= ENV['NA_EDITOR'] || ENV['GIT_EDITOR'] || ENV['EDITOR']
7
- else
8
- editor ||= ENV['NA_EDITOR'] || ENV['EDITOR'] || ENV['GIT_EDITOR']
9
- end
9
+ editor ||= if prefer_git_editor
10
+ ENV['NA_EDITOR'] || ENV['GIT_EDITOR'] || ENV.fetch('EDITOR', nil)
11
+ else
12
+ ENV['NA_EDITOR'] || ENV['EDITOR'] || ENV.fetch('GIT_EDITOR', nil)
13
+ end
10
14
 
11
15
  return editor if editor&.good? && TTY::Which.exist?(editor)
12
16
 
13
- NA.notify("No EDITOR environment variable, testing available editors", debug: true)
17
+ NA.notify('No EDITOR environment variable, testing available editors', debug: true)
14
18
  editors = %w[vim vi code subl mate mvim nano emacs]
15
19
  editors.each do |ed|
16
20
  try = TTY::Which.which(ed)
@@ -43,11 +47,10 @@ module NA
43
47
  "#{editor} #{args.join(' ')}"
44
48
  end
45
49
 
46
- ##
47
- ## Create a process for an editor and wait for the file handle to return
48
- ##
49
- ## @param input [String] Text input for editor
50
- ##
50
+ # Create a process for an editor and wait for the file handle to return
51
+ #
52
+ # @param input [String] Text input for editor
53
+ # @return [String] Edited text
51
54
  def fork_editor(input = '', message: :default)
52
55
  # raise NonInteractive, 'Non-interactive terminal' unless $stdout.isatty || ENV['DOING_EDITOR_TEST']
53
56
 
@@ -78,8 +81,8 @@ module NA
78
81
  Process.wait(pid)
79
82
 
80
83
  begin
81
- if $?.exitstatus == 0
82
- input = IO.read(tmpfile.path)
84
+ if $CHILD_STATUS.exitstatus.zero?
85
+ input = File.read(tmpfile.path)
83
86
  else
84
87
  exit_now! 'Cancelled'
85
88
  end
@@ -88,16 +91,13 @@ module NA
88
91
  tmpfile.unlink
89
92
  end
90
93
 
91
- input.split(/\n/).delete_if(&:ignore?).join("\n")
94
+ input.split("\n").delete_if(&:ignore?).join("\n")
92
95
  end
93
96
 
94
- ##
95
- ## Takes a multi-line string and formats it as an entry
96
- ##
97
- ## @param input [String] The string to parse
98
- ##
99
- ## @return [Array] [[String]title, [Note]note]
100
- ##
97
+ # Takes a multi-line string and formats it as an entry
98
+ #
99
+ # @param input [String] The string to parse
100
+ # @return [Array] [[String]title, [Note]note]
101
101
  def format_input(input)
102
102
  NA.notify("#{NA.theme[:error]}No content in entry", exit_code: 1) if input.nil? || input.strip.empty?
103
103
 
@@ -108,7 +108,7 @@ module NA
108
108
  title = title.expand_date_tags
109
109
 
110
110
  note = if input_lines.length > 1
111
- input_lines[1..-1]
111
+ input_lines[1..]
112
112
  else
113
113
  []
114
114
  end
data/lib/na/hash.rb CHANGED
@@ -1,15 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class ::Hash
4
+ # Convert all keys in the hash to symbols recursively
5
+ #
6
+ # @return [Hash] Hash with symbolized keys
4
7
  def symbolize_keys
5
8
  each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
6
9
  end
7
10
 
8
- ##
9
- ## Freeze all values in a hash
10
- ##
11
- ## @return Hash with all values frozen
12
- ##
11
+ #
12
+ # Freeze all values in a hash
13
+ #
14
+ # @return Hash with all values frozen
13
15
  def deep_freeze
14
16
  chilled = {}
15
17
  each do |k, v|
@@ -19,10 +21,16 @@ class ::Hash
19
21
  chilled.freeze
20
22
  end
21
23
 
24
+ # Freeze all values in a hash in place
25
+ #
26
+ # @return [Hash] Hash with all values frozen
22
27
  def deep_freeze!
23
28
  replace deep_thaw.deep_freeze
24
29
  end
25
30
 
31
+ # Recursively duplicate all values in a hash
32
+ #
33
+ # @return [Hash] Hash with all values duplicated
26
34
  def deep_thaw
27
35
  chilled = {}
28
36
  each do |k, v|
@@ -32,12 +40,27 @@ class ::Hash
32
40
  chilled.dup
33
41
  end
34
42
 
43
+ # Recursively duplicate all values in a hash in place
44
+ #
45
+ # @return [Hash] Hash with all values duplicated
35
46
  def deep_thaw!
36
47
  replace deep_thaw
37
48
  end
38
49
 
39
- def deep_merge(second)
40
- merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : Array === v1 && Array === v2 ? v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2 }
41
- merge(second.to_h, &merger)
42
- end
50
+ # Recursively merge two hashes, combining arrays and preferring non-nil values
51
+ #
52
+ # @param second [Hash] The hash to merge with
53
+ # @return [Hash] The merged hash
54
+ def deep_merge(second)
55
+ merger = proc { |_, v1, v2|
56
+ if v1.is_a?(Hash) && v2.is_a?(Hash)
57
+ v1.merge(v2, &merger)
58
+ elsif v1.is_a?(Array) && v2.is_a?(Array)
59
+ v1 | v2
60
+ else
61
+ [:undefined, nil, :nil].include?(v2) ? v1 : v2
62
+ end
63
+ }
64
+ merge(second.to_h, &merger)
65
+ end
43
66
  end
@@ -4,7 +4,15 @@ module GLI
4
4
  module Commands
5
5
  # Help Command Monkeypatch for paginated output
6
6
  class Help < Command
7
- def show_help(global_options, options, arguments, out, error)
7
+ # Show help output for GLI commands with paginated output
8
+ #
9
+ # @param global_options [Hash] Global CLI options
10
+ # @param options [Hash] Command-specific options
11
+ # @param arguments [Array] Command arguments
12
+ # @param out [IO] Output stream
13
+ # @param error [IO] Error stream
14
+ # @return [void]
15
+ def show_help(_global_options, options, arguments, out, error)
8
16
  NA::Pager.paginate = true
9
17
 
10
18
  command_finder = HelpModules::CommandFinder.new(@app, arguments, error)