howzit 2.0.9 → 2.0.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: 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)