howzit 2.0.9 → 2.0.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: e11e4e9f19c26666de0d9dbfbfd0ab1b3c1bee495575fe0962ab5157a185e613
4
- data.tar.gz: 7430c1da6d45f7dc873e01a8c1e6618da2dab503659921b9b72d690ad7aff181
3
+ metadata.gz: 700c444e11477c62c6de0f06b81b3029b21f3363f507c5a6cab927f2365d2be5
4
+ data.tar.gz: be862c2c31427abae2428a024009aea0f73885a5dc9652c5d2b5f308c181ca3b
5
5
  SHA512:
6
- metadata.gz: 739879dd1a53689e005511745003dd9b9d2ff5a8fa964cec3cb1754ef78e740c488d0a9040cee6b6de5e5eaf62a4a9c50dcc74a236db732e1c78ef4f9c8978cd
7
- data.tar.gz: '009601634be888e709ee5709fc2bb990b5dff1a274144469621a9b23dc5108778fec2f8838fc46f03999df8c31d9872f3ad8b9de57afb6bb534035a8f8070c25'
6
+ metadata.gz: d07c309680eef72f5edb3b41d40354aa9cefa1b6efebd1c0dfbaa59f7cbfc95c6211433f081b6f21afa069cd5ae30f79dc42f1a4a08c96680b533b16519ca25b
7
+ data.tar.gz: '08ca9d51ec3793c1750d70f500bde1e68c1f327cd41d0b92839c317d6a421f96cbb8ef2f03204ab6cfe1ddb1d83a497084aa9590233d6ceb860921fdb473267e'
data/.travis.yml CHANGED
@@ -10,4 +10,4 @@ install:
10
10
  - gem uninstall bundler
11
11
  - gem install bundler --version '2.2.29'
12
12
  - bundle install
13
- script: "rake"
13
+ script: "bundle exec rspec spec --exclude-pattern 'cli*'"
data/CHANGELOG.md CHANGED
@@ -1,3 +1,28 @@
1
+ ### 2.0.12
2
+
3
+ 2022-08-05 11:27
4
+
5
+ #### IMPROVED
6
+
7
+ - Show how many tasks will be included when requesting confirmation for an include? directive
8
+
9
+ ### 2.0.11
10
+
11
+ 2022-08-05 11:21
12
+
13
+ #### IMPROVED
14
+
15
+ - Update fish completions with all current command line options
16
+
17
+ ### 2.0.10
18
+
19
+ 2022-08-05 08:17
20
+
21
+ #### IMPROVED
22
+
23
+ - Provide more helpful feedback if no content is found in build note
24
+ - Confirm whether the user wants to create a new note when one isn't found
25
+
1
26
  ### 2.0.9
2
27
 
3
28
  2022-08-05 07:29
@@ -1,3 +1,35 @@
1
1
  complete -xc howzit -a "(howzit -L)"
2
- complete -xc howzit -s r -a "(howzit -T)"
3
-
2
+ complete -c howzit -l default -d "Answer all prompts with default response"
3
+ complete -c howzit -x -s m -l matching -a "partial exact fuzzy beginswith" -d "Topics matching type"
4
+ complete -c howzit -x -l multiple -a "first best all choose" -d "Multiple result handling"
5
+ complete -c howzit -s u -l no-upstream -d "Don't traverse up parent directories for additional build notes"
6
+ complete -c howzit -s u -l upstream -d "Traverse up parent directories for additional build notes"
7
+ complete -c howzit -s L -l list-completions -d "List topics for completion"
8
+ complete -c howzit -s l -l list -d "List available topics"
9
+ complete -c howzit -s R -l list-runnable -d "List topics containing @ directives (verbose)"
10
+ complete -c howzit -s T -l task-list -d "List topics containing @ directives (completion-compatible)"
11
+ complete -c howzit -l templates -d "List available templates"
12
+ complete -c howzit -l title-only -d "Output title only"
13
+ complete -c howzit -s c -l create -d "Create a skeleton build note in the current working directory"
14
+ complete -c howzit -f -l config-get -d "Display the configuration settings or setting for a specific key"
15
+ complete -c howzit -f -l config-set -d "Set a config value (must be a valid key)"
16
+ complete -c howzit -l edit-config -d "Edit configuration file using editor (subl)"
17
+ complete -c howzit -s e -l edit -d "Edit buildnotes file in current working directory using editor (subl)"
18
+ complete -c howzit -x -l grep -d "Display sections matching a search pattern"
19
+ complete -c howzit -s r -l run -a "(howzit -T)" -d "Execute @run, @open, and/or @copy commands for given topic"
20
+ complete -c howzit -s s -l select -d "Select topic from menu"
21
+ complete -c howzit -l color -d "Colorize output (default on)"
22
+ complete -c howzit -l no-color -d "Don't colorize output (default on)"
23
+ complete -c howzit -x -l header-format -d "Formatting style for topic titles (border, block)"
24
+ complete -c howzit -l md-highlight -d "Highlight Markdown syntax (default on), requires mdless or mdcat"
25
+ complete -c howzit -l no-md-highlight -d "Don't highlight Markdown syntax (default on), requires mdless or mdcat"
26
+ complete -c howzit -l pager -d "Paginate output (default on)"
27
+ complete -c howzit -l no-pager -d "Don't paginate output (default on)"
28
+ complete -c howzit -l show-code -d "Display the content of fenced run blocks"
29
+ complete -c howzit -s t -l title -d "Output title with build notes"
30
+ complete -c howzit -x -s w -l wrap -d "Wrap to specified width (default 80, 0 to disable)"
31
+ complete -c howzit -s d -l debug -d "Show debug messages (and all messages)"
32
+ complete -c howzit -s q -l quiet -d "Silence info message"
33
+ complete -c howzit -l verbose -d "Show all messages"
34
+ complete -c howzit -s h -l help -d "Display this screen"
35
+ complete -c howzit -s v -l version -d "Display version number"
@@ -7,10 +7,27 @@ module Howzit
7
7
 
8
8
  attr_reader :metadata, :title
9
9
 
10
+ ##
11
+ ## Initialize a build note
12
+ ##
13
+ ## @param file [String] The path to the build note file
14
+ ## @param args [Array] additional args
15
+ ##
10
16
  def initialize(file: nil, args: [])
11
17
  @topics = []
12
- create_note if note_file.nil?
13
- @metadata = Util.read_file(note_file).split(/^#/)[0].strip.get_metadata
18
+ if note_file.nil?
19
+ res = Prompt.yn('No build notes file found, create one?', default: true)
20
+
21
+ create_note if res
22
+ Process.exit 0
23
+ end
24
+ content = Util.read_file(note_file)
25
+ if content.nil? || content.empty?
26
+ Howzit.console.error("{br}No content found in build note (#{note_file}){x}".c)
27
+ Process.exit 1
28
+ else
29
+ @metadata = content.split(/^#/)[0].strip.get_metadata
30
+ end
14
31
 
15
32
  read_help(file)
16
33
  end
@@ -19,14 +36,25 @@ module Howzit
19
36
  puts "#<Howzit::BuildNote @topics=[#{@topics.count}]>"
20
37
  end
21
38
 
39
+ ##
40
+ ## Public method to begin processing the build note based on command line options
41
+ ##
22
42
  def run
23
43
  process
24
44
  end
25
45
 
46
+ ##
47
+ ## Public method to open build note in editor
48
+ ##
26
49
  def edit
27
50
  edit_note
28
51
  end
29
52
 
53
+ ##
54
+ ## Find a topic based on a fuzzy match
55
+ ##
56
+ ## @param term [String] The search term
57
+ ##
30
58
  def find_topic(term)
31
59
  @topics.filter do |topic|
32
60
  rx = term.to_rx
@@ -34,11 +62,19 @@ module Howzit
34
62
  end
35
63
  end
36
64
 
65
+ ##
66
+ ## Call grep on all topics, filtering out those that don't match
67
+ ##
68
+ ## @param term [String] The search pattern
69
+ ##
37
70
  def grep(term)
38
71
  @topics.filter { |topic| topic.grep(term) }
39
72
  end
40
73
 
41
74
  # Output a list of topic titles
75
+ #
76
+ # @return [String] formatted list of topics in build note
77
+ #
42
78
  def list
43
79
  output = []
44
80
  output.push("{bg}Topics:{x}\n".c)
@@ -48,14 +84,32 @@ module Howzit
48
84
  output.join("\n")
49
85
  end
50
86
 
87
+
88
+ ##
89
+ ## Return an array of topic titles
90
+ ##
91
+ ## @return [Array] array of topic titles
92
+ ##
51
93
  def list_topics
52
94
  @topics.map { |topic| topic.title }
53
95
  end
54
96
 
97
+ ##
98
+ ## Return a list of topic titles suitable for shell completion
99
+ ##
100
+ ## @return [String] newline-separated list of topic titles
101
+ ##
55
102
  def list_completions
56
103
  list_topics.join("\n")
57
104
  end
58
105
 
106
+ ##
107
+ ## Return a list of topics containing @directives,
108
+ ## suitable for shell completion
109
+ ##
110
+ ## @return [String] newline-separated list of topic
111
+ ## titles
112
+ ##
59
113
  def list_runnable_completions
60
114
  output = []
61
115
  @topics.each do |topic|
@@ -64,6 +118,12 @@ module Howzit
64
118
  output.join("\n")
65
119
  end
66
120
 
121
+ ##
122
+ ## Return a formatted list of topics containing
123
+ ## directives suitable for console output
124
+ ##
125
+ ## @return [String] formatted list
126
+ ##
67
127
  def list_runnable
68
128
  output = []
69
129
  output.push(%({bg}"Runnable" Topics:{x}\n).c)
@@ -82,6 +142,11 @@ module Howzit
82
142
  output.join("\n")
83
143
  end
84
144
 
145
+ ##
146
+ ## Read the help file contents
147
+ ##
148
+ ## @param file [String] The filepath
149
+ ##
85
150
  def read_file(file)
86
151
  read_help_file(file)
87
152
  end
@@ -210,28 +275,24 @@ module Howzit
210
275
  buildnotes.reverse
211
276
  end
212
277
 
213
- def build_note?(filename)
214
- return false if filename.downcase !~ /^(howzit[^.]*|build[^.]+)/
215
-
216
- return false if Howzit.config.should_ignore(filename)
217
-
218
- true
219
- end
220
-
278
+ ##
279
+ ## Glob current directory for valid build note filenames
280
+ ## (must start with "build" or "howzit" and have
281
+ ## extension of "txt", "md", or "markdown")
282
+ ##
283
+ ## @return [String] file path
284
+ ##
221
285
  def glob_note
222
- filename = nil
223
- # Check for a build note file in the current folder. Filename must start
224
- # with "build" and have an extension of txt, md, or markdown.
225
-
226
- Dir.glob('*.{txt,md,markdown}').each do |f|
227
- if build_note?(f)
228
- filename = f
229
- break
230
- end
231
- end
232
- filename
286
+ Dir.glob('*.{txt,md,markdown}').select(&:build_note?)[0]
233
287
  end
234
288
 
289
+ ##
290
+ ## Search for a valid build note, checking current
291
+ ## directory, git top level directory, and parent
292
+ ## directories
293
+ ##
294
+ ## @return [String] filepath
295
+ ##
235
296
  def find_note_file
236
297
  filename = glob_note
237
298
 
@@ -253,6 +314,11 @@ module Howzit
253
314
  File.expand_path(filename)
254
315
  end
255
316
 
317
+ ##
318
+ ## Search upstream directories for build notes
319
+ ##
320
+ ## @return [Array] array of build note paths
321
+ ##
256
322
  def read_upstream
257
323
  buildnotes = glob_upstream
258
324
 
@@ -263,6 +329,13 @@ module Howzit
263
329
  topics_dict
264
330
  end
265
331
 
332
+ ##
333
+ ## Test to ensure that any `required` metadata in a
334
+ ## template is fulfilled by the build note
335
+ ##
336
+ ## @param template [String] The template to read
337
+ ## from
338
+ ##
266
339
  def ensure_requirements(template)
267
340
  t_leader = Util.read_file(template).split(/^#/)[0].strip
268
341
  if t_leader.length > 0
@@ -271,8 +344,8 @@ module Howzit
271
344
  required = t_meta['required'].strip.split(/\s*,\s*/)
272
345
  required.each do |req|
273
346
  unless @metadata.keys.include?(req.downcase)
274
- Howzit.console.error %({xr}ERROR: Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}).c
275
- Howzit.console.error %({xr}Please define {by}#{req.downcase}{xr} in build notes{x}).c
347
+ Howzit.console.error %({bRw}ERROR:{xbr} Missing required metadata key from template '{bw}#{File.basename(template, '.md')}{xr}'{x}).c
348
+ Howzit.console.error %({br}Please define {by}#{req.downcase}{xr} in build notes{x}).c
276
349
  Process.exit 1
277
350
  end
278
351
  end
@@ -280,6 +353,11 @@ module Howzit
280
353
  end
281
354
  end
282
355
 
356
+ ##
357
+ ## Read a list of topics from an included template
358
+ ##
359
+ ## @param content [String] The template contents
360
+ ##
283
361
  def get_template_topics(content)
284
362
  leader = content.split(/^#/)[0].strip
285
363
 
@@ -327,10 +405,16 @@ module Howzit
327
405
  template_topics
328
406
  end
329
407
 
330
- def include_file(m)
331
- file = File.expand_path(m[1])
408
+ ##
409
+ ## Import the contents of a filename as new topics
410
+ ##
411
+ ## @param mtch [MatchData] the filename match from
412
+ ## the include directive
413
+ ##
414
+ def include_file(mtch)
415
+ file = File.expand_path(mtch[1])
332
416
 
333
- return m[0] unless File.exist?(file)
417
+ return mtch[0] unless File.exist?(file)
334
418
 
335
419
  content = Util.read_file(file)
336
420
  home = ENV['HOME']
@@ -345,6 +429,11 @@ module Howzit
345
429
  end
346
430
  end
347
431
 
432
+ ##
433
+ ## Get the title of the build note (top level header)
434
+ ##
435
+ ## @param truncate [Integer] Truncate to width
436
+ ##
348
437
  def note_title(truncate = 0)
349
438
  help = Util.read_file(note_file)
350
439
  title = help.match(/(?:^(\S.*?)(?=\n==)|^# ?(.*?)$)/)
@@ -357,7 +446,13 @@ module Howzit
357
446
  title && truncate.positive? ? title.trunc(truncate) : title
358
447
  end
359
448
 
360
- # Read in the build notes file and output a hash of "Title" => contents
449
+ # Read in the build notes file and output a hash of
450
+ # "Title" => contents
451
+ #
452
+ # @param path [String] The build note path
453
+ #
454
+ # @return [Array] array of Topics
455
+ #
361
456
  def read_help_file(path = nil)
362
457
  topics = []
363
458
 
@@ -365,6 +460,11 @@ module Howzit
365
460
 
366
461
  help = Util.read_file(filename)
367
462
 
463
+ if help.nil? || help.empty?
464
+ Howzit.console.error("{br}No content found in #{filename}{x}".c)
465
+ Process.exit 1
466
+ end
467
+
368
468
  @title = note_title
369
469
 
370
470
  help.gsub!(/@include\((.*?)\)/) do
@@ -403,6 +503,11 @@ module Howzit
403
503
  topics
404
504
  end
405
505
 
506
+ ##
507
+ ## Read build note and include upstream topics
508
+ ##
509
+ ## @param path [String] The build note path
510
+ ##
406
511
  def read_help(path = nil)
407
512
  @topics = read_help_file(path)
408
513
  return unless path.nil? && Howzit.options[:include_upstream]
@@ -412,8 +517,17 @@ module Howzit
412
517
  upstream_topics.each do |topic|
413
518
  @topics.push(topic) unless find_topic(title.sub(/^.+:/, '')).count.positive?
414
519
  end
520
+
521
+ if note_file && @topics.empty?
522
+ Howzit.console.error("{br}Note file found but no topics detected in #{note_file}{x}".c)
523
+ Process.exit 1
524
+ end
525
+
415
526
  end
416
527
 
528
+ ##
529
+ ## Open build note in editor
530
+ ##
417
531
  def edit_note
418
532
  editor = Howzit.options.fetch(:editor, ENV['EDITOR'])
419
533
 
@@ -431,7 +545,16 @@ module Howzit
431
545
  end
432
546
  end
433
547
 
434
- def process_topic(topic, run, single = false)
548
+ ##
549
+ ## Run or print a topic
550
+ ##
551
+ ## @param topic [Topic] The topic
552
+ ## @param run [Boolean] execute directives if
553
+ ## true
554
+ ## @param single [Boolean] is being output as a
555
+ ## single topic
556
+ ##
557
+ def process_topic(topic, run, single: false)
435
558
  new_topic = topic.dup
436
559
 
437
560
  # Handle variable replacement
@@ -445,6 +568,9 @@ module Howzit
445
568
  output.nil? ? '' : output.join("\n")
446
569
  end
447
570
 
571
+ ##
572
+ ## Search and process the build note
573
+ ##
448
574
  def process
449
575
  output = []
450
576
 
@@ -537,10 +663,10 @@ module Howzit
537
663
 
538
664
  if !topic_matches.empty?
539
665
  # If we found a match
540
- topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], true)) }
666
+ topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], single: true)) }
541
667
  else
542
668
  # If there's no argument or no match found, output all
543
- topics.each { |k| output.push(process_topic(k, false, false)) }
669
+ topics.each { |k| output.push(process_topic(k, false, single: false)) }
544
670
  end
545
671
  Howzit.options[:paginate] = false if Howzit.options[:run]
546
672
  Util.show(output.join("\n").strip, Howzit.options)
data/lib/howzit/colors.rb CHANGED
@@ -4,7 +4,9 @@
4
4
  module Howzit
5
5
  # Terminal output color functions.
6
6
  module Color
7
+ # Regexp to match excape sequences
7
8
  ESCAPE_REGEX = /(?<=\[)(?:(?:(?:[349]|10)[0-9]|[0-9])?;?)+(?=m)/.freeze
9
+
8
10
  # All available color names. Available as methods and string extensions.
9
11
  #
10
12
  # @example Use a color as a method. Color reset will be added to end of string.
@@ -79,6 +81,7 @@ module Howzit
79
81
  [:default, '0;39']
80
82
  ].map(&:freeze).freeze
81
83
 
84
+ # Array of attribute keys only
82
85
  ATTRIBUTE_NAMES = ATTRIBUTES.transpose.first
83
86
 
84
87
  # Returns true if Howzit::Color supports the +feature+.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Available log levels
3
4
  LOG_LEVELS = {
4
5
  debug: 0,
5
6
  info: 1,
data/lib/howzit/hash.rb CHANGED
@@ -5,7 +5,7 @@ class ::Hash
5
5
  ##
6
6
  ## Freeze all values in a hash
7
7
  ##
8
- ## @return Hash with all values frozen
8
+ ## @return [Hash] Hash with all values frozen
9
9
  ##
10
10
  def deep_freeze
11
11
  chilled = {}
@@ -16,10 +16,18 @@ class ::Hash
16
16
  chilled.freeze
17
17
  end
18
18
 
19
+ ##
20
+ ## Deep freeze a hash in place (destructive)
21
+ ##
19
22
  def deep_freeze!
20
23
  replace deep_thaw.deep_freeze
21
24
  end
22
25
 
26
+ ##
27
+ ## Unfreeze nested hash values
28
+ ##
29
+ ## @return [Hash] Hash with all values unfrozen
30
+ ##
23
31
  def deep_thaw
24
32
  chilled = {}
25
33
  each do |k, v|
@@ -29,27 +37,40 @@ class ::Hash
29
37
  chilled.dup
30
38
  end
31
39
 
40
+ ##
41
+ ## Unfreeze nested hash values in place (destructive)
42
+ ##
32
43
  def deep_thaw!
33
44
  replace deep_thaw
34
45
  end
35
46
 
36
47
  # Turn all keys into string
37
48
  #
38
- # Return a copy of the hash where all its keys are strings
49
+ # @return [Hash] hash with all keys as strings
50
+ #
39
51
  def stringify_keys
40
52
  each_with_object({}) { |(k, v), hsh| hsh[k.to_s] = v.is_a?(Hash) ? v.stringify_keys : v }
41
53
  end
42
54
 
55
+ ##
56
+ ## Turn all keys into strings in place (destructive)
57
+ ##
43
58
  def stringify_keys!
44
- replace stringify_keys
59
+ replace stringify_keys
45
60
  end
46
61
 
47
62
  # Turn all keys into symbols
63
+ #
64
+ # @return [Hash] hash with all keys as symbols
65
+ #
48
66
  def symbolize_keys
49
67
  each_with_object({}) { |(k, v), hsh| hsh[k.to_sym] = v.is_a?(Hash) ? v.symbolize_keys : v }
50
68
  end
51
69
 
70
+ ##
71
+ ## Turn all keys into symbols in place (destructive)
72
+ ##
52
73
  def symbolize_keys!
53
- replace symbolize_keys
74
+ replace symbolize_keys
54
75
  end
55
76
  end
data/lib/howzit/prompt.rb CHANGED
@@ -4,10 +4,22 @@ module Howzit
4
4
  # Command line prompt utils
5
5
  module Prompt
6
6
  class << self
7
- def yn(prompt, default: true)
7
+
8
+ ##
9
+ ## Display and read a Yes/No prompt
10
+ ##
11
+ ## @param prompt [String] The prompt string
12
+ ## @param default [Boolean] default value if
13
+ ## return is pressed or prompt is
14
+ ## skipped
15
+ ##
16
+ ## @return [Boolean] result
17
+ ##
18
+ def yn(prompt, default: true)
8
19
  return default unless $stdout.isatty
9
20
 
10
21
  return default if Howzit.options[:default]
22
+
11
23
  tty_state = `stty -g`
12
24
  system 'stty raw -echo cbreak isig'
13
25
  yn = color_single_options(default ? %w[Y n] : %w[y N])
@@ -20,6 +32,14 @@ module Howzit
20
32
  res.empty? ? default : res =~ /y/i
21
33
  end
22
34
 
35
+ ##
36
+ ## Helper function to colorize the Y/N prompt
37
+ ##
38
+ ## @param choices [Array] The choices with
39
+ ## default capitalized
40
+ ##
41
+ ## @return [String] colorized string
42
+ ##
23
43
  def color_single_options(choices = %w[y n])
24
44
  out = []
25
45
  choices.each do |choice|
@@ -33,6 +53,12 @@ module Howzit
33
53
  Color.template("{xg}[#{out.join('/')}{xg}]{x}")
34
54
  end
35
55
 
56
+ ##
57
+ ## Create a numbered list of options. Outputs directly
58
+ ## to console, returns nothing
59
+ ##
60
+ ## @param matches [Array] The list items
61
+ ##
36
62
  def options_list(matches)
37
63
  counter = 1
38
64
  puts
@@ -43,6 +69,15 @@ module Howzit
43
69
  puts
44
70
  end
45
71
 
72
+ ##
73
+ ## Choose from a list of items. If fzf is available,
74
+ ## uses that, otherwise generates its own list of
75
+ ## options and accepts a numeric response
76
+ ##
77
+ ## @param matches [Array] The options list
78
+ ##
79
+ ## @return [Array] the selected results
80
+ ##
46
81
  def choose(matches)
47
82
  if Util.command_exist?('fzf')
48
83
  settings = [
@@ -80,7 +115,7 @@ module Howzit
80
115
  end
81
116
  line = line == '' ? 1 : line.to_i
82
117
 
83
- return matches[line - 1] if line.positive? && line <= matches.length
118
+ return [matches[line - 1]] if line.positive? && line <= matches.length
84
119
 
85
120
  puts 'Out of range'
86
121
  options_list(matches)