howzit 1.2.11 → 1.2.12

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: 24b630c81dbd81b43b8572bbc91baabe96063a17c3cb6295a8026e6a3176dd39
4
- data.tar.gz: 04c98ab9c7a9c57e8c7536e7709b57d51b965a76cc315c846c46dca3a0a2105e
3
+ metadata.gz: 875d039552876588bf298e4297ffec3955a6a73da6c2f89f608f7d11ed9d4e93
4
+ data.tar.gz: ecd7f0dd8f67999de4e223745f79096cc06ebd84e867400bc729f2323e8f8360
5
5
  SHA512:
6
- metadata.gz: 3ce4d9a3cd8cd8970cd37e5b90c7154c0b66de06827780c4e38bb4f358d517b8c81d720383cbaa8fa84ff74f56a9f1f60e554f92614cf12e4f1f563092d9d651
7
- data.tar.gz: 5187fbd097d9171ddd3756f9df92e62b0df5309d7e1d2e0aef26551f6641340d65ade5f49f1c9b0a47852e6913c64d5914d334732e15a1ac71f2277edde38fe7
6
+ metadata.gz: bfcad0fc1a4fbf0140dc91ae5cf03e52c3d42aed4a404c91fab642db05d9849b92c0c73d4f87d79ca0b58d1583a6a0ab5ef0ef9936812913567aeb3f24508a93
7
+ data.tar.gz: db44b03b2f2c25c84187692a9d46a328a22d4a7dce0adc20304ab31fe58e91b6ca4bef6848ab69efcce5e0c60e9c8955501f4232d5643f664d35cb81da9bcbdf
data/.gitignore CHANGED
@@ -41,3 +41,4 @@ Gemfile.lock
41
41
 
42
42
  # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
43
43
  .rvmrc
44
+ results.log
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ### 1.2.12
2
+
3
+ 2022-08-01 16:23
4
+
5
+ #### IMPROVED
6
+
7
+ - Replace ANSI escape codes with color template system
8
+ - When @including an external file, if the file doesn't contain any level 2+ headers, import it as plain text.
9
+
1
10
  ### 1.2.11
2
11
 
3
12
  2022-08-01 08:23
data/Rakefile CHANGED
@@ -18,3 +18,14 @@ RuboCop::RakeTask.new do |t|
18
18
  end
19
19
 
20
20
  YARD::Rake::YardocTask.new
21
+
22
+ desc 'Development version check'
23
+ task :ver do
24
+ gver = `git ver`
25
+ cver = IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
26
+ res = `grep VERSION lib/howzit/version.rb`
27
+ version = res.match(/VERSION *= *['"](\d+\.\d+\.\d+(\w+)?)/)[1]
28
+ puts "git tag: #{gver}"
29
+ puts "version.rb: #{version}"
30
+ puts "changelog: #{cver}"
31
+ end
data/bin/howzit CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env ruby -W1
2
2
  # frozen_string_literal: true
3
3
 
4
4
  $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
@@ -6,4 +6,3 @@ require 'howzit'
6
6
 
7
7
  how = Howzit::BuildNotes.new(ARGV)
8
8
  how.process
9
-
@@ -2,8 +2,9 @@ module Howzit
2
2
  # Primary Class for this module
3
3
  class BuildNotes
4
4
  include Prompt
5
+ include Color
5
6
 
6
- attr_accessor :cli_args, :arguments, :metadata
7
+ attr_accessor :cli_args, :options, :arguments, :metadata
7
8
 
8
9
  def topics
9
10
  @topics ||= read_help
@@ -40,8 +41,8 @@ module Howzit
40
41
  # favoring environment settings
41
42
  def which_pager
42
43
  if @options[:pager] =~ /auto/i
43
- pagers = [ENV['GIT_PAGER'], ENV['PAGER'],
44
- 'bat', 'less', 'more', 'cat', 'pager']
44
+ pagers = [ENV['PAGER'], ENV['GIT_PAGER'],
45
+ 'bat', 'less', 'more', 'pager']
45
46
  pagers.delete_if(&:nil?).select!(&:available?)
46
47
  return nil if pagers.empty?
47
48
 
@@ -145,12 +146,12 @@ module Howzit
145
146
  choices.each do |choice|
146
147
  case choice
147
148
  when /[A-Z]/
148
- out.push("\e[1;32m#{choice}\e[0;32m")
149
+ out.push(Color.template("{bg}#{choice}{xg}"))
149
150
  else
150
- out.push(choice)
151
+ out.push(Color.template("{w}#{choice}"))
151
152
  end
152
153
  end
153
- "\e[0;32m[#{out.join('/')}]\e[0m"
154
+ Color.template("{g}[#{out.join('/')}{g}]{x}")
154
155
  end
155
156
 
156
157
  # Create a buildnotes skeleton
@@ -161,7 +162,7 @@ module Howzit
161
162
  end
162
163
  # First make sure there isn't already a buildnotes file
163
164
  if note_file
164
- fname = "\e[1;33m#{note_file}\e[1;37m"
165
+ fname = Color.template("{by}#{note_file}{bw}")
165
166
  res = yn("#{fname} exists and appears to be a build note, continue anyway?", false)
166
167
  unless res
167
168
  puts 'Canceled'
@@ -170,19 +171,19 @@ module Howzit
170
171
  end
171
172
 
172
173
  title = File.basename(Dir.pwd)
173
- printf "\e[1;37mProject name \e[0;32m[#{title}]\e[1;37m: \e[0m"
174
- input = STDIN.gets.chomp
174
+ printf Color.template("{bw}Project name {xg}[#{title}]{bw}: {x}")
175
+ input = $stdin.gets.chomp
175
176
  title = input unless input.empty?
176
177
 
177
178
  summary = ''
178
- printf "\e[1;37mProject summary: \e[0m"
179
- input = STDIN.gets.chomp
179
+ printf Color.template('{bw}Project summary: {x}')
180
+ input = $stdin.gets.chomp
180
181
  summary = input unless input.empty?
181
182
 
182
- filename = 'buildnotes.md'
183
- printf "\e[1;37mBuild notes filename (must begin with 'howzit' or 'build')\n\e[0;32m[#{filename}]\e[1;37m: \e[0m"
184
- input = STDIN.gets.chomp
185
- filename = input unless input.empty?
183
+ fname = 'buildnotes.md'
184
+ printf Color.template("{bw}Build notes filename (must begin with 'howzit' or 'build')\n{xg}[#{fname}]{bw}: {x}")
185
+ input = $stdin.gets.chomp
186
+ fname = input unless input.empty?
186
187
 
187
188
  note = <<~EOBUILDNOTES
188
189
  # #{title}
@@ -209,8 +210,8 @@ module Howzit
209
210
 
210
211
  EOBUILDNOTES
211
212
 
212
- if File.exist?(filename)
213
- file = "\e[1;33m#{filename}"
213
+ if File.exist?(fname)
214
+ file = Color.template("{by}#{fname}")
214
215
  res = yn("Are you absolutely sure you want to overwrite #{file}", false)
215
216
 
216
217
  unless res
@@ -219,9 +220,9 @@ module Howzit
219
220
  end
220
221
  end
221
222
 
222
- File.open(filename, 'w') do |f|
223
+ File.open(fname, 'w') do |f|
223
224
  f.puts note
224
- puts "Build notes for #{title} written to #{filename}"
225
+ puts Color.template("{by}Build notes for #{title} written to #{fname}")
225
226
  end
226
227
  end
227
228
 
@@ -229,8 +230,8 @@ module Howzit
229
230
  def format_header(title, opts = {})
230
231
  options = {
231
232
  hr: "\u{254C}",
232
- color: '1;32',
233
- border: '0',
233
+ color: '{bg}',
234
+ border: '{x}',
234
235
  mark: false
235
236
  }
236
237
 
@@ -239,29 +240,29 @@ module Howzit
239
240
  cols = TTY::Screen.columns
240
241
 
241
242
  cols = @options[:wrap] if (@options[:wrap]).positive? && cols > @options[:wrap]
242
- title = "\e[#{options[:border]}m#{options[:hr] * 2}( \e[#{options[:color]}m#{title}\e[#{options[:border]}m )"
243
+ title = Color.template("#{options[:border]}#{options[:hr] * 2}( #{options[:color]}#{title}#{options[:border]} )")
243
244
 
244
245
  tail = if should_mark_iterm?
245
246
  "#{options[:hr] * (cols - title.uncolor.length - 15)}#{options[:mark] ? iterm_marker : ''}"
246
247
  else
247
248
  options[:hr] * (cols - title.uncolor.length)
248
249
  end
249
- "#{title}#{tail}\e[0m"
250
+ Color.template("#{title}#{tail}{x}")
250
251
  end
251
252
 
252
253
  def os_open(command)
253
254
  os = RbConfig::CONFIG['target_os']
254
- out = "\e[1;32mOpening \e[3;37m#{command}"
255
+ out = Color.template("{bg}Opening {bw}#{command}")
255
256
  case os
256
257
  when /darwin.*/i
257
- warn "#{out} (macOS)\e[0m" if @options[:log_level] < 2
258
+ warn Color.template("#{out} (macOS){x}") if @options[:log_level] < 2
258
259
  `open #{Shellwords.escape(command)}`
259
260
  when /mingw|mswin/i
260
- warn "#{out} (Windows)\e[0m" if @options[:log_level] < 2
261
+ warn Color.template("#{out} (Windows){x}") if @options[:log_level] < 2
261
262
  `start #{Shellwords.escape(command)}`
262
263
  else
263
264
  if 'xdg-open'.available?
264
- warn "#{out} (Linux)\e[0m" if @options[:log_level] < 2
265
+ warn Color.template("#{out} (Linux){x}") if @options[:log_level] < 2
265
266
  `xdg-open #{Shellwords.escape(command)}`
266
267
  else
267
268
  warn out if @options[:log_level] < 2
@@ -300,7 +301,7 @@ module Howzit
300
301
  directives.each do |c|
301
302
  if c[0].nil?
302
303
  title = c[3] ? c[3].strip : ''
303
- warn "\e[1;32mRunning block \e[3;37m#{title}\e[0m" if @options[:log_level] < 2
304
+ warn Color.template("{bg}Running block {bw}#{title}{x}") if @options[:log_level] < 2
304
305
  block = c[4].strip
305
306
  script = Tempfile.new('howzit_script')
306
307
  begin
@@ -322,18 +323,18 @@ module Howzit
322
323
  warn "No topic match for @include(#{search})"
323
324
  else
324
325
  if @included.include?(matches[0])
325
- warn "\e[1;33mTasks from \e[3;37m#{matches[0]} already included, skipping\e[0m" if @options[:log_level] < 2
326
+ warn Color.template("{by}Tasks from {bw}#{matches[0]} already included, skipping{x}") if @options[:log_level] < 2
326
327
  else
327
- warn "\e[1;33mIncluding tasks from \e[3;37m#{matches[0]}\e[0m" if @options[:log_level] < 2
328
+ warn Color.template("{by}Including tasks from {bw}#{matches[0]}{x}") if @options[:log_level] < 2
328
329
  process_topic(matches[0], true)
329
- warn "\e[1;33mEnd include \e[3;37m#{matches[0]}\e[0m" if @options[:log_level] < 2
330
+ warn Color.template("{by}End include {bw}#{matches[0]}{x}") if @options[:log_level] < 2
330
331
  end
331
332
  end
332
333
  when /run/i
333
- warn "\e[1;32mRunning \e[3;37m#{obj}\e[0m" if @options[:log_level] < 2
334
+ warn Color.template("{bg}Running {bw}#{obj}{x}") if @options[:log_level] < 2
334
335
  system(obj)
335
336
  when /copy/i
336
- warn "\e[1;32mCopied \e[3;37m#{obj}\e[1;32m to clipboard\e[0m" if @options[:log_level] < 2
337
+ warn Color.template("{bg}Copied {bw}#{obj}{bg} to clipboard{x}") if @options[:log_level] < 2
337
338
  `echo #{Shellwords.escape(obj)}'\\c'|pbcopy`
338
339
  when /open|url/i
339
340
  os_open(obj)
@@ -341,7 +342,7 @@ module Howzit
341
342
  end
342
343
  end
343
344
  else
344
- warn "\e[0;31m--run: No \e[1;31m@directive\e[0;31;40m found in \e[1;37m#{key}\e[0m"
345
+ warn Color.template("{r}--run: No {br}@directive{xr} found in {bw}#{key}{x}")
345
346
  end
346
347
  output.push("Ran #{tasks} #{tasks == 1 ? 'task' : 'tasks'}") if @options[:log_level] < 2
347
348
 
@@ -371,12 +372,12 @@ module Howzit
371
372
  unless matches.empty?
372
373
  if opt[:single]
373
374
  title = "From #{matches[0]}:"
374
- color = '33;40'
375
- rule = '30;40'
375
+ color = '{yK}'
376
+ rule = '{kK}'
376
377
  else
377
378
  title = "Include #{matches[0]}"
378
- color = '33;40'
379
- rule = '0'
379
+ color = '{yK}'
380
+ rule = '{x}'
380
381
  end
381
382
  output.push(format_header("#{'> ' * @nest_level}#{title}", { color: color, hr: '.', border: rule })) unless @included.include?(matches[0])
382
383
 
@@ -405,15 +406,15 @@ module Howzit
405
406
  when /open|url/
406
407
  "\u{279A}"
407
408
  end
408
- output.push("\e[1;35;40m#{icon} \e[3;37;40m#{obj}\e[0m")
409
+ output.push(Color.template("{bmK}#{icon} {bwK}#{obj}{x}"))
409
410
  when /(`{3,})run *(.*?)$/i
410
411
  m = Regexp.last_match
411
412
  desc = m[2].length.positive? ? "Block: #{m[2]}" : 'Code Block'
412
- output.push("\e[1;35;40m\u{25B6} \e[3;37;40m#{desc}\e[0m\n```")
413
+ output.push(Color.template("{bmK}\u{25B6} {bwK}#{desc}{x}\n```"))
413
414
  when /@@@run *(.*?)$/i
414
415
  m = Regexp.last_match
415
416
  desc = m[1].length.positive? ? "Block: #{m[1]}" : 'Code Block'
416
- output.push("\e[1;35;40m\u{25B6} \e[3;37;40m#{desc}\e[0m")
417
+ output.push(Color.template("{bmK}\u{25B6} {bwK}#{desc}{x}"))
417
418
  else
418
419
  l.wrap!(@options[:wrap]) if (@options[:wrap]).positive?
419
420
  output.push(l)
@@ -444,9 +445,9 @@ module Howzit
444
445
  # Output a list of topic titles
445
446
  def list_topics
446
447
  output = []
447
- output.push("\e[1;32mTopics:\e[0m\n")
448
+ output.push(Color.template("{bg}Topics:{x}\n"))
448
449
  topics.each_key do |title|
449
- output.push("- \e[1;37m#{title}\e[0m")
450
+ output.push(Color.template("- {bw}#{title}{x}"))
450
451
  end
451
452
  output.join("\n")
452
453
  end
@@ -456,14 +457,14 @@ module Howzit
456
457
  topics.keys.join("\n")
457
458
  end
458
459
 
459
- def get_note_title(filename, truncate = 0)
460
+ def get_note_title(truncate = 0)
460
461
  title = nil
461
- help = IO.read(filename).strip
462
+ help = IO.read(note_file).strip
462
463
  title = help.match(/(?:^(\S.*?)(?=\n==)|^# ?(.*?)$)/)
463
464
  title = if title
464
465
  title[1].nil? ? title[2] : title[1]
465
466
  else
466
- filename.sub(/(\.\w+)?$/, '')
467
+ note_file.sub(/(\.\w+)?$/, '')
467
468
  end
468
469
 
469
470
  title && truncate.positive? ? title.trunc(truncate) : title
@@ -486,7 +487,7 @@ module Howzit
486
487
 
487
488
  def list_runnable
488
489
  output = []
489
- output.push(%(\e[1;32m"Runnable" Topics:\e[0m\n))
490
+ output.push(Color.template(%({bg}"Runnable" Topics:{x}\n)))
490
491
  topics.each do |title, sect|
491
492
  s_out = []
492
493
  lines = sect.split(/\n/)
@@ -507,7 +508,7 @@ module Howzit
507
508
  end
508
509
  end
509
510
  unless s_out.empty?
510
- output.push("- \e[1;37m#{title}\e[0m")
511
+ output.push(Color.template("- {bw}#{title}{x}"))
511
512
  output.push(s_out.join("\n"))
512
513
  end
513
514
  end
@@ -531,8 +532,8 @@ module Howzit
531
532
  required = t_meta['required'].strip.split(/\s*,\s*/)
532
533
  required.each do |req|
533
534
  unless @metadata.keys.include?(req.downcase)
534
- warn %(\e[0;31mERROR: Missing required metadata key from template '\e[1;37m#{File.basename(template, '.md')}\e[0;31m'\e[0m)
535
- warn %(\e[0;31mPlease define \e[1;33m#{req.downcase}\e[0;31m in build notes\e[0m)
535
+ warn Color.template(%({xr}ERROR: Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}))
536
+ warn Color.template(%({xr}Please define {by}#{req.downcase}{xr} in build notes{x}))
536
537
  Process.exit 1
537
538
  end
538
539
  end
@@ -581,6 +582,24 @@ module Howzit
581
582
  template_topics
582
583
  end
583
584
 
585
+ def include_file(m)
586
+ file = File.expand_path(m[1])
587
+
588
+ return m[0] unless File.exist?(file)
589
+
590
+ content = IO.read(file)
591
+ home = ENV['HOME']
592
+ short_path = File.dirname(file.sub(/^#{home}/, '~'))
593
+ prefix = "#{short_path}/#{File.basename(file)}:"
594
+ parts = content.split(/^##+/)
595
+ parts.shift
596
+ if parts.empty?
597
+ content
598
+ else
599
+ "## #{parts.join('## ')}".gsub(/^(##+ *)(?=\S)/, "\\1#{prefix}")
600
+ end
601
+ end
602
+
584
603
  # Read in the build notes file and output a hash of "Title" => contents
585
604
  def read_help_file(path = nil)
586
605
  filename = path.nil? ? note_file : path
@@ -588,21 +607,7 @@ module Howzit
588
607
  help = IO.read(filename)
589
608
 
590
609
  help.gsub!(/@include\((.*?)\)/) do
591
- m = Regexp.last_match
592
- file = File.expand_path(m[1])
593
- if File.exist?(file)
594
- content = IO.read(file)
595
- home = ENV['HOME']
596
- short_path = File.dirname(file.sub(/^#{home}/, '~'))
597
- prefix = "#{short_path}:"
598
- parts = content.split(/^##+/)
599
- parts.shift
600
- content = '## ' + parts.join('## ')
601
- content.gsub!(/^(##+ *)(?=\S)/, "\\1#{prefix}")
602
- content
603
- else
604
- m[0]
605
- end
610
+ include_file(Regexp.last_match)
606
611
  end
607
612
 
608
613
  template_topics = get_template_topics(help)
@@ -674,6 +679,7 @@ module Howzit
674
679
  end
675
680
 
676
681
  def initialize(args = [])
682
+ Color.coloring = $stdout.isatty
677
683
  flags = {
678
684
  run: false,
679
685
  list_topics: false,
@@ -779,8 +785,8 @@ module Howzit
779
785
  @options[:log_level] = 0
780
786
  end
781
787
 
782
- opts.on('-u', '--upstream', 'Traverse up parent directories for additional build notes') do
783
- @options[:include_upstream] = true
788
+ opts.on('-u', '--[no-]upstream', 'Traverse up parent directories for additional build notes') do |p|
789
+ @options[:include_upstream] = p
784
790
  end
785
791
 
786
792
  opts.on('--show-code', 'Display the content of fenced run blocks') do
@@ -805,21 +811,21 @@ module Howzit
805
811
  Dir.chdir(template_folder)
806
812
  Dir.glob('*.md').each do |file|
807
813
  template = File.basename(file, '.md')
808
- puts "\e[7;30;45mtemplate: \e[7;33;40m#{template}\e[0m"
809
- puts "\e[1;30m[\e[1;37mtasks\e[1;30m]──────────────────────────────────────┐\e[0m"
814
+ puts Color.template("{Mk}template:{Yk}#{template}{x}")
815
+ puts Color.template("{bk}[{bl}tasks{bk}]──────────────────────────────────────┐{x}")
810
816
  metadata = file.extract_metadata
811
817
  topics = read_help_file(file)
812
818
  topics.each_key do |topic|
813
- puts " \e[1;30m│\e[1;37m-\e[0m \e[1;36;40m#{template}:#{topic.sub(/^.*?:/, '')}\e[0m"
819
+ puts Color.template(" {bk}│{bw}-{x} {bcK}#{template}:#{topic.sub(/^.*?:/, '')}{x}")
814
820
  end
815
821
  if metadata.size > 0
816
822
  meta = []
817
- meta << metadata['required'].split(/\s*,\s*/).map {|m| "*\e[1;37m#{m}\e[0;37m" } if metadata.key?('required')
823
+ meta << metadata['required'].split(/\s*,\s*/).map {|m| "*{bw}#{m}{xw}" } if metadata.key?('required')
818
824
  meta << metadata['optional'].split(/\s*,\s*/).map {|m| "#{m}" } if metadata.key?('optional')
819
- puts "\e[1;30m[\e[1;34mmeta\e[1;30m]───────────────────────────────────────┤\e[0m"
820
- puts " \e[1;30m│\e[1;37m \e[0;37m#{meta.join(", ")}\e[0m"
825
+ puts Color.template("{bk}[{bl}meta{bk}]───────────────────────────────────────┤{x}")
826
+ puts Color.template(" {bk}│ {xw}#{meta.join(", ")}{x}")
821
827
  end
822
- puts " \e[1;30m└───────────────────────────────────────────┘\e[0m"
828
+ puts Color.template(" {bk}└───────────────────────────────────────────┘{x}")
823
829
  end
824
830
  Process.exit 0
825
831
  end
@@ -1083,13 +1089,13 @@ module Howzit
1083
1089
  end
1084
1090
 
1085
1091
  if @options[:title_only]
1086
- out = get_note_title(note_file, 20)
1092
+ out = get_note_title(20)
1087
1093
  $stdout.print(out.strip)
1088
1094
  Process.exit(0)
1089
1095
  elsif @options[:output_title]
1090
- title = get_note_title(note_file)
1096
+ title = get_note_title
1091
1097
  if title && !title.empty?
1092
- header = format_header(title, { hr: "\u{2550}", color: '1;37;40' })
1098
+ header = format_header(title, { hr: "\u{2550}", color: '{bwK}' })
1093
1099
  output.push("#{header}\n")
1094
1100
  end
1095
1101
  end
@@ -1127,8 +1133,8 @@ module Howzit
1127
1133
  matches = match_topic(search)
1128
1134
 
1129
1135
  if matches.empty?
1130
- output.push(%(\e[0;31mERROR: No topic match found for \e[1;33m#{search}\e[0m\n))
1131
- if !@options[:show_all_on_error]
1136
+ output.push(Color.template(%({bR}ERROR:{xr} No topic match found for {bw}#{search}{x}\n)))
1137
+ unless @options[:show_all_on_error]
1132
1138
  show(output.join("\n"), { color: true, highlight: false, paginate: false, wrap: 0 })
1133
1139
  Process.exit 1
1134
1140
  end
@@ -0,0 +1,323 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Cribbed from <https://github.com/flori/term-ansicolor>
4
+ module Howzit
5
+ # Terminal output color functions.
6
+ module Color
7
+ ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/.freeze
8
+ # All available color names. Available as methods and string extensions.
9
+ #
10
+ # @example Use a color as a method. Color reset will be added to end of string.
11
+ # Color.yellow('This text is yellow') => "\e[33mThis text is yellow\e[0m"
12
+ #
13
+ # @example Use a color as a string extension. Color reset added automatically.
14
+ # 'This text is green'.green => "\e[1;32mThis text is green\e[0m"
15
+ #
16
+ # @example Send a text string as a color
17
+ # Color.send('red') => "\e[31m"
18
+ ATTRIBUTES = [
19
+ [:clear, 0], # String#clear is already used to empty string in Ruby 1.9
20
+ [:reset, 0], # synonym for :clear
21
+ [:bold, 1],
22
+ [:dark, 2],
23
+ [:italic, 3], # not widely implemented
24
+ [:underline, 4],
25
+ [:underscore, 4], # synonym for :underline
26
+ [:blink, 5],
27
+ [:rapid_blink, 6], # not widely implemented
28
+ [:negative, 7], # no reverse because of String#reverse
29
+ [:concealed, 8],
30
+ [:strikethrough, 9], # not widely implemented
31
+ [:strike, 9], # not widely implemented
32
+ [:black, 30],
33
+ [:red, 31],
34
+ [:green, 32],
35
+ [:yellow, 33],
36
+ [:blue, 34],
37
+ [:magenta, 35],
38
+ [:purple, 35],
39
+ [:cyan, 36],
40
+ [:white, 37],
41
+ [:bgblack, 40],
42
+ [:bgred, 41],
43
+ [:bggreen, 42],
44
+ [:bgyellow, 43],
45
+ [:bgblue, 44],
46
+ [:bgmagenta, 45],
47
+ [:bgpurple, 45],
48
+ [:bgcyan, 46],
49
+ [:bgwhite, 47],
50
+ [:boldblack, 90],
51
+ [:boldred, 91],
52
+ [:boldgreen, 92],
53
+ [:boldyellow, 93],
54
+ [:boldblue, 94],
55
+ [:boldmagenta, 95],
56
+ [:boldpurple, 95],
57
+ [:boldcyan, 96],
58
+ [:boldwhite, 97],
59
+ [:boldbgblack, 100],
60
+ [:boldbgred, 101],
61
+ [:boldbggreen, 102],
62
+ [:boldbgyellow, 103],
63
+ [:boldbgblue, 104],
64
+ [:boldbgmagenta, 105],
65
+ [:boldbgpurple, 105],
66
+ [:boldbgcyan, 106],
67
+ [:boldbgwhite, 107],
68
+ [:softpurple, '0;35;40'],
69
+ [:hotpants, '7;34;40'],
70
+ [:knightrider, '7;30;40'],
71
+ [:flamingo, '7;31;47'],
72
+ [:yeller, '1;37;43'],
73
+ [:whiteboard, '1;30;47'],
74
+ [:chalkboard, '1;37;40'],
75
+ [:led, '0;32;40'],
76
+ [:redacted, '0;30;40'],
77
+ [:alert, '1;31;43'],
78
+ [:error, '1;37;41'],
79
+ [:default, '0;39']
80
+ ].map(&:freeze).freeze
81
+
82
+ ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
83
+
84
+ # Returns true if Howzit::Color supports the +feature+.
85
+ #
86
+ # The feature :clear, that is mixing the clear color attribute into String,
87
+ # is only supported on ruby implementations, that do *not* already
88
+ # implement the String#clear method. It's better to use the reset color
89
+ # attribute instead.
90
+ def support?(feature)
91
+ case feature
92
+ when :clear
93
+ !String.instance_methods(false).map(&:to_sym).include?(:clear)
94
+ end
95
+ end
96
+
97
+ # Template coloring
98
+ class ::String
99
+ ##
100
+ ## Extract the longest valid %color name from a string.
101
+ ##
102
+ ## Allows %colors to bleed into other text and still
103
+ ## be recognized, e.g. %greensomething still finds
104
+ ## %green.
105
+ ##
106
+ ## @return [String] a valid color name
107
+ ##
108
+ def validate_color
109
+ valid_color = nil
110
+ compiled = ''
111
+ normalize_color.split('').each do |char|
112
+ compiled += char
113
+ valid_color = compiled if Color.attributes.include?(compiled.to_sym) || compiled =~ /^([fb]g?)?#([a-f0-9]{6})$/i
114
+ end
115
+
116
+ valid_color
117
+ end
118
+
119
+ ##
120
+ ## Normalize a color name, removing underscores,
121
+ ## replacing "bright" with "bold", and converting
122
+ ## bgbold to boldbg
123
+ ##
124
+ ## @return [String] Normalized color name
125
+ ##
126
+ def normalize_color
127
+ gsub(/_/, '').sub(/bright/i, 'bold').sub(/bgbold/, 'boldbg')
128
+ end
129
+
130
+ # Get the calculated ANSI color at the end of the
131
+ # string
132
+ #
133
+ # @return ANSI escape sequence to match color
134
+ #
135
+ def last_color_code
136
+ m = scan(ESCAPE_REGEX)
137
+
138
+ em = ['0']
139
+ fg = nil
140
+ bg = nil
141
+ rgbf = nil
142
+ rgbb = nil
143
+
144
+ m.each do |c|
145
+ case c
146
+ when '0'
147
+ em = ['0']
148
+ fg, bg, rgbf, rgbb = nil
149
+ when /^[34]8/
150
+ case c
151
+ when /^3/
152
+ fg = nil
153
+ rgbf = c
154
+ when /^4/
155
+ bg = nil
156
+ rgbb = c
157
+ end
158
+ else
159
+ c.split(/;/).each do |i|
160
+ x = i.to_i
161
+ if x <= 9
162
+ em << x
163
+ elsif x >= 30 && x <= 39
164
+ rgbf = nil
165
+ fg = x
166
+ elsif x >= 40 && x <= 49
167
+ rgbb = nil
168
+ bg = x
169
+ elsif x >= 90 && x <= 97
170
+ rgbf = nil
171
+ fg = x
172
+ elsif x >= 100 && x <= 107
173
+ rgbb = nil
174
+ bg = x
175
+ end
176
+ end
177
+ end
178
+ end
179
+
180
+ escape = "\e[#{em.join(';')}m"
181
+ escape += "\e[#{rgbb}m" if rgbb
182
+ escape += "\e[#{rgbf}m" if rgbf
183
+ escape + "\e[#{[fg, bg].delete_if(&:nil?).join(';')}m"
184
+ end
185
+ end
186
+
187
+ class << self
188
+ # Returns true if the coloring function of this module
189
+ # is switched on, false otherwise.
190
+ def coloring?
191
+ @coloring
192
+ end
193
+
194
+ attr_writer :coloring
195
+
196
+ ##
197
+ ## Enables colored output
198
+ ##
199
+ ## @example Turn color on or off based on TTY
200
+ ## Howzit::Color.coloring = STDOUT.isatty
201
+ def coloring
202
+ @coloring ||= true
203
+ end
204
+
205
+ ##
206
+ ## Convert a template string to a colored string.
207
+ ## Colors are specified with single letters inside
208
+ ## curly braces. Uppercase changes background color.
209
+ ##
210
+ ## w: white, k: black, g: green, l: blue, y: yellow, c: cyan,
211
+ ## m: magenta, r: red, b: bold, u: underline, i: italic,
212
+ ## x: reset (remove background, color, emphasis)
213
+ ##
214
+ ## @example Convert a templated string
215
+ ## Color.template('{Rwb}Warning:{x} {w}you look a little {g}ill{x}')
216
+ ##
217
+ ## @param input [String, Array] The template
218
+ ## string. If this is an array, the
219
+ ## elements will be joined with a
220
+ ## space.
221
+ ##
222
+ ## @return [String] Colorized string
223
+ ##
224
+ def template(input)
225
+ input = input.join(' ') if input.is_a? Array
226
+ input.gsub!(/%/, '%%')
227
+ fmt = input.gsub(/\{(\w+)\}/) do
228
+ Regexp.last_match(1).split('').map { |c| "%<#{c}>s" }.join('')
229
+ end
230
+
231
+ colors = { w: white, k: black, g: green, l: blue,
232
+ y: yellow, c: cyan, m: magenta, r: red,
233
+ W: bgwhite, K: bgblack, G: bggreen, L: bgblue,
234
+ Y: bgyellow, C: bgcyan, M: bgmagenta, R: bgred,
235
+ b: bold, u: underline, i: italic, x: reset }
236
+
237
+ format(fmt, colors)
238
+ end
239
+ end
240
+
241
+ ATTRIBUTES.each do |c, v|
242
+ new_method = <<-EOSCRIPT
243
+ def #{c}(string = nil)
244
+ result = ''
245
+ result << "\e[#{v}m" if Howzit::Color.coloring?
246
+ if block_given?
247
+ result << yield
248
+ elsif string.respond_to?(:to_str)
249
+ result << string.to_str
250
+ elsif respond_to?(:to_str)
251
+ result << to_str
252
+ else
253
+ return result #only switch on
254
+ end
255
+ result << "\e[0m" if Howzit::Color.coloring?
256
+ result
257
+ end
258
+ EOSCRIPT
259
+
260
+ module_eval(new_method)
261
+
262
+ next unless c =~ /bold/
263
+
264
+ # Accept brightwhite in addition to boldwhite
265
+ new_method = <<-EOSCRIPT
266
+ def #{c.to_s.sub(/bold/, 'bright')}(string = nil)
267
+ result = ''
268
+ result << "\e[#{v}m" if Howzit::Color.coloring?
269
+ if block_given?
270
+ result << yield
271
+ elsif string.respond_to?(:to_str)
272
+ result << string.to_str
273
+ elsif respond_to?(:to_str)
274
+ result << to_str
275
+ else
276
+ return result #only switch on
277
+ end
278
+ result << "\e[0m" if Howzit::Color.coloring?
279
+ result
280
+ end
281
+ EOSCRIPT
282
+
283
+ module_eval(new_method)
284
+ end
285
+
286
+ def rgb(hex)
287
+ is_bg = hex.match(/^bg?#/) ? true : false
288
+ hex_string = hex.sub(/^([fb]g?)?#/, '')
289
+
290
+ parts = hex_string.match(/(?<r>..)(?<g>..)(?<b>..)/)
291
+ t = []
292
+ %w[r g b].each do |e|
293
+ t << parts[e].hex
294
+ end
295
+ color =
296
+ "\e[#{is_bg ? '48' : '38'};2;#{t.join(';')}m"
297
+ end
298
+
299
+ # Regular expression that is used to scan for ANSI-sequences while
300
+ # uncoloring strings.
301
+ COLORED_REGEXP = /\e\[(?:(?:[349]|10)[0-7]|[0-9])?m/.freeze
302
+
303
+ # Returns an uncolored version of the string, that is all
304
+ # ANSI-sequences are stripped from the string.
305
+ def uncolor(string = nil) # :yields:
306
+ if block_given?
307
+ yield.to_str.gsub(COLORED_REGEXP, '')
308
+ elsif string.respond_to?(:to_str)
309
+ string.to_str.gsub(COLORED_REGEXP, '')
310
+ elsif respond_to?(:to_str)
311
+ to_str.gsub(COLORED_REGEXP, '')
312
+ else
313
+ ''
314
+ end
315
+ end
316
+
317
+ # Returns an array of all Howzit::Color attributes as symbols.
318
+ def attributes
319
+ ATTRIBUTE_NAMES
320
+ end
321
+ extend self
322
+ end
323
+ end
@@ -2,5 +2,5 @@
2
2
  # Primary module for this gem.
3
3
  module Howzit
4
4
  # Current Howzit version.
5
- VERSION = '1.2.11'.freeze
5
+ VERSION = '1.2.12'.freeze
6
6
  end
data/lib/howzit.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'howzit/version'
2
2
  require 'howzit/prompt'
3
+ require 'howzit/colors'
3
4
  require 'howzit/buildnotes'
4
5
  require 'howzit/stringutils'
5
6
  require 'optparse'
@@ -9,3 +9,36 @@ describe Howzit::BuildNotes do
9
9
  end
10
10
  end
11
11
  end
12
+
13
+ describe Howzit::BuildNotes do
14
+ Dir.chdir('spec')
15
+ subject { Howzit::BuildNotes.new(['--no-upstream']) }
16
+
17
+ describe ".note_file" do
18
+ it "locates a build note file" do
19
+ expect(subject.note_file).not_to be_empty
20
+ end
21
+ end
22
+
23
+ describe ".get_note_title" do
24
+ it "is named howzit test" do
25
+ expect(subject.get_note_title).to match(/howzit test/)
26
+ end
27
+ end
28
+
29
+ describe ".grep_topics" do
30
+ it "found editable" do
31
+ expect(subject.grep_topics('editable')).to include('File Structure')
32
+ expect(subject.grep_topics('editable')).not_to include('Build')
33
+ end
34
+ end
35
+
36
+ describe ".list_topic_titles" do
37
+ it "found 4 topics" do
38
+ expect(subject.topics.keys.count).to eq 4
39
+ end
40
+ it "outputs a newline-separated string" do
41
+ expect(subject.list_topic_titles.scan(/\n/).count).to eq 3
42
+ end
43
+ end
44
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: howzit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.11
4
+ version: 1.2.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brett Terpstra
@@ -269,6 +269,7 @@ files:
269
269
  - lib/.rubocop.yml
270
270
  - lib/howzit.rb
271
271
  - lib/howzit/buildnotes.rb
272
+ - lib/howzit/colors.rb
272
273
  - lib/howzit/prompt.rb
273
274
  - lib/howzit/stringutils.rb
274
275
  - lib/howzit/version.rb