howzit 1.2.11 → 1.2.12

Sign up to get free protection for your applications and to get access to all the features.
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