howzit 2.1.16 → 2.1.21
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 +4 -4
- data/CHANGELOG.md +74 -0
- data/bin/howzit +67 -67
- data/howzit.gemspec +3 -1
- data/lib/howzit/buildnote.rb +224 -38
- data/lib/howzit/prompt.rb +19 -7
- data/lib/howzit/run_report.rb +51 -0
- data/lib/howzit/stringutils.rb +3 -1
- data/lib/howzit/task.rb +21 -2
- data/lib/howzit/topic.rb +24 -1
- data/lib/howzit/util.rb +5 -1
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +11 -2
- data/spec/buildnote_spec.rb +59 -3
- data/spec/cli_spec.rb +1 -1
- data/spec/run_report_spec.rb +35 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/topic_spec.rb +5 -2
- metadata +6 -18
- data/.editorconfig +0 -9
- data/.github/FUNDING.yml +0 -2
- data/.gitignore +0 -45
- data/.howzit.taskpaper.bak +0 -27
- data/.irbrc +0 -6
- data/.rspec +0 -2
- data/.rubocop.yml +0 -48
- data/.travis.yml +0 -17
- data/.yardopts +0 -6
- data/lib/.rubocop.yml +0 -1
- data/spec/.rubocop.yml +0 -4
data/lib/howzit/buildnote.rb
CHANGED
|
@@ -70,9 +70,39 @@ module Howzit
|
|
|
70
70
|
def find_topic(term = nil)
|
|
71
71
|
return @topics if term.nil?
|
|
72
72
|
|
|
73
|
+
rx = term.to_rx
|
|
74
|
+
|
|
75
|
+
@topics.filter do |topic|
|
|
76
|
+
title = topic.title.downcase.sub(/ *\(.*?\) *$/, '')
|
|
77
|
+
match = title =~ rx
|
|
78
|
+
|
|
79
|
+
if !match && term =~ /[,:]/
|
|
80
|
+
normalized = title.gsub(/\s*([,:])\s*/, '\1')
|
|
81
|
+
match = normalized =~ rx
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
match
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
## Find a topic with an exact whole-word match
|
|
90
|
+
##
|
|
91
|
+
## @param term [String] The search term
|
|
92
|
+
##
|
|
93
|
+
## @return [Array] Array of topics that exactly match the term
|
|
94
|
+
##
|
|
95
|
+
def find_topic_exact(term = nil)
|
|
96
|
+
return [] if term.nil?
|
|
97
|
+
|
|
73
98
|
@topics.filter do |topic|
|
|
74
|
-
|
|
75
|
-
|
|
99
|
+
title = topic.title.downcase.sub(/ *\(.*?\) *$/, '').strip
|
|
100
|
+
# Split both the title and search term into words
|
|
101
|
+
title_words = title.split
|
|
102
|
+
search_words = term.split
|
|
103
|
+
|
|
104
|
+
# Check if all search words match the title words exactly (case-insensitive)
|
|
105
|
+
search_words.map(&:downcase) == title_words.map(&:downcase)
|
|
76
106
|
end
|
|
77
107
|
end
|
|
78
108
|
|
|
@@ -115,7 +145,11 @@ module Howzit
|
|
|
115
145
|
## @return [Array] array of topic titles
|
|
116
146
|
##
|
|
117
147
|
def list_topics
|
|
118
|
-
@topics.map
|
|
148
|
+
@topics.map do |topic|
|
|
149
|
+
title = topic.title
|
|
150
|
+
title += "(#{topic.named_args.keys.join(', ')})" unless topic.named_args.empty?
|
|
151
|
+
title
|
|
152
|
+
end
|
|
119
153
|
end
|
|
120
154
|
|
|
121
155
|
##
|
|
@@ -137,7 +171,11 @@ module Howzit
|
|
|
137
171
|
def list_runnable_completions
|
|
138
172
|
output = []
|
|
139
173
|
@topics.each do |topic|
|
|
140
|
-
|
|
174
|
+
next unless topic.tasks.count.positive?
|
|
175
|
+
|
|
176
|
+
title = topic.title
|
|
177
|
+
title += "(#{topic.named_args.keys.join(', ')})" unless topic.named_args.empty?
|
|
178
|
+
output.push(title)
|
|
141
179
|
end
|
|
142
180
|
output.join("\n")
|
|
143
181
|
end
|
|
@@ -159,7 +197,10 @@ module Howzit
|
|
|
159
197
|
|
|
160
198
|
next if s_out.empty?
|
|
161
199
|
|
|
162
|
-
|
|
200
|
+
title = topic.title
|
|
201
|
+
title += " {dy}({xy}#{topic.named_args.keys.join(', ')}{dy}){x}" unless topic.named_args.empty?
|
|
202
|
+
|
|
203
|
+
output.push("- {g}#{title}{x}".c)
|
|
163
204
|
output.push(s_out.join("\n"))
|
|
164
205
|
end
|
|
165
206
|
|
|
@@ -328,6 +369,70 @@ module Howzit
|
|
|
328
369
|
|
|
329
370
|
private
|
|
330
371
|
|
|
372
|
+
def topic_search_terms_from_cli
|
|
373
|
+
args = Howzit.cli_args || []
|
|
374
|
+
raw = args.join(' ').strip
|
|
375
|
+
return [] if raw.empty?
|
|
376
|
+
|
|
377
|
+
smart_split_topics(raw).map { |term| term.strip.downcase }.reject(&:empty?)
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def smart_split_topics(raw)
|
|
381
|
+
segments, separators = segments_and_separators_for(raw)
|
|
382
|
+
return segments if separators.empty?
|
|
383
|
+
|
|
384
|
+
combined = []
|
|
385
|
+
current = segments.shift || ''
|
|
386
|
+
|
|
387
|
+
separators.each_with_index do |separator, idx|
|
|
388
|
+
next_segment = segments[idx] || ''
|
|
389
|
+
if keep_separator_with_current?(current, separator, next_segment)
|
|
390
|
+
current = "#{current}#{separator}#{next_segment}"
|
|
391
|
+
else
|
|
392
|
+
combined << current
|
|
393
|
+
current = next_segment
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
combined << current
|
|
398
|
+
combined
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
def segments_and_separators_for(raw)
|
|
402
|
+
segments = []
|
|
403
|
+
separators = []
|
|
404
|
+
current = String.new
|
|
405
|
+
|
|
406
|
+
raw.each_char do |char|
|
|
407
|
+
if char =~ /[,:]/
|
|
408
|
+
segments << current
|
|
409
|
+
separators << char
|
|
410
|
+
current = String.new
|
|
411
|
+
else
|
|
412
|
+
current << char
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
segments << current
|
|
417
|
+
[segments, separators]
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
def keep_separator_with_current?(current, separator, next_segment)
|
|
421
|
+
candidate = "#{current}#{separator}#{next_segment}"
|
|
422
|
+
normalized_candidate = normalize_separator_string(candidate)
|
|
423
|
+
return false if normalized_candidate.empty?
|
|
424
|
+
|
|
425
|
+
@topics.any? do |topic|
|
|
426
|
+
normalize_separator_string(topic.title).start_with?(normalized_candidate)
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def normalize_separator_string(string)
|
|
431
|
+
return '' if string.nil?
|
|
432
|
+
|
|
433
|
+
string.downcase.gsub(/\s+/, ' ').strip.gsub(/\s*([,:])\s*/, '\1')
|
|
434
|
+
end
|
|
435
|
+
|
|
331
436
|
##
|
|
332
437
|
## Import the contents of a filename as new topics
|
|
333
438
|
##
|
|
@@ -718,6 +823,10 @@ module Howzit
|
|
|
718
823
|
##
|
|
719
824
|
def process
|
|
720
825
|
output = []
|
|
826
|
+
if Howzit.options[:run]
|
|
827
|
+
Howzit.run_log = []
|
|
828
|
+
Howzit.multi_topic_run = false
|
|
829
|
+
end
|
|
721
830
|
|
|
722
831
|
unless note_file
|
|
723
832
|
Process.exit 0 if Howzit.options[:list_runnable_titles] || Howzit.options[:list_topic_titles]
|
|
@@ -757,62 +866,139 @@ module Howzit
|
|
|
757
866
|
Process.exit(0)
|
|
758
867
|
end
|
|
759
868
|
|
|
760
|
-
|
|
761
|
-
|
|
869
|
+
# Handle grep and choose modes (batch all results)
|
|
762
870
|
if Howzit.options[:grep]
|
|
871
|
+
topic_matches = []
|
|
763
872
|
matches = grep(Howzit.options[:grep])
|
|
764
873
|
case Howzit.options[:multiple_matches]
|
|
765
874
|
when :all
|
|
766
875
|
topic_matches.concat(matches.sort_by(&:title))
|
|
767
876
|
else
|
|
768
|
-
topic_matches.concat(Prompt.choose(matches.map(&:title), height: :max))
|
|
877
|
+
topic_matches.concat(Prompt.choose(matches.map(&:title), height: :max, query: Howzit.options[:grep]))
|
|
769
878
|
end
|
|
879
|
+
process_topic_matches(topic_matches, output)
|
|
770
880
|
elsif Howzit.options[:choose]
|
|
881
|
+
topic_matches = []
|
|
771
882
|
titles = Prompt.choose(list_topics, height: :max)
|
|
772
883
|
titles.each { |title| topic_matches.push(find_topic(title)[0]) }
|
|
773
|
-
|
|
884
|
+
process_topic_matches(topic_matches, output)
|
|
774
885
|
elsif !Howzit.cli_args.empty?
|
|
775
|
-
|
|
886
|
+
# Collect all topic matches first (showing menus as needed)
|
|
887
|
+
search = topic_search_terms_from_cli
|
|
888
|
+
topic_matches = collect_topic_matches(search, output)
|
|
889
|
+
process_topic_matches(topic_matches, output)
|
|
890
|
+
else
|
|
891
|
+
# No arguments - show all topics
|
|
892
|
+
if Howzit.options[:run]
|
|
893
|
+
Howzit.run_log = []
|
|
894
|
+
Howzit.multi_topic_run = topics.length > 1
|
|
895
|
+
end
|
|
896
|
+
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
897
|
+
finalize_output(output)
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
##
|
|
902
|
+
## Collect all topic matches from search terms, showing menus as needed
|
|
903
|
+
## but not displaying/running until all selections are made
|
|
904
|
+
##
|
|
905
|
+
## @param search_terms [Array] Array of search term strings
|
|
906
|
+
## @param output [Array] Output array for error messages
|
|
907
|
+
##
|
|
908
|
+
## @return [Array] Array of all matched topics
|
|
909
|
+
##
|
|
910
|
+
def collect_topic_matches(search_terms, output)
|
|
911
|
+
all_matches = []
|
|
776
912
|
|
|
777
|
-
|
|
778
|
-
|
|
913
|
+
search_terms.each do |s|
|
|
914
|
+
# First check for exact whole-word matches
|
|
915
|
+
exact_matches = find_topic_exact(s)
|
|
779
916
|
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
topic_matches.push(matches[0])
|
|
786
|
-
when :best
|
|
787
|
-
topic_matches.push(matches.sort_by { |a| [a.title.comp_distance(s), a.title.length] })
|
|
788
|
-
when :all
|
|
789
|
-
topic_matches.concat(matches)
|
|
790
|
-
else
|
|
791
|
-
titles = matches.map(&:title)
|
|
792
|
-
res = Prompt.choose(titles)
|
|
793
|
-
old_matching = Howzit.options[:matching]
|
|
794
|
-
Howzit.options[:matching] = 'exact'
|
|
795
|
-
res.each { |title| topic_matches.concat(find_topic(title)) }
|
|
796
|
-
Howzit.options[:matching] = old_matching
|
|
797
|
-
end
|
|
798
|
-
end
|
|
799
|
-
end
|
|
917
|
+
topic_matches = if !exact_matches.empty?
|
|
918
|
+
exact_matches
|
|
919
|
+
else
|
|
920
|
+
resolve_fuzzy_matches(s, output)
|
|
921
|
+
end
|
|
800
922
|
|
|
801
|
-
if topic_matches.empty?
|
|
802
|
-
|
|
803
|
-
|
|
923
|
+
if topic_matches.empty?
|
|
924
|
+
output.push(%({bR}ERROR:{xr} No topic match found for {bw}#{s}{x}\n).c)
|
|
925
|
+
else
|
|
926
|
+
all_matches.concat(topic_matches)
|
|
804
927
|
end
|
|
805
928
|
end
|
|
806
929
|
|
|
930
|
+
all_matches
|
|
931
|
+
end
|
|
932
|
+
|
|
933
|
+
##
|
|
934
|
+
## Resolve fuzzy matches for a search term
|
|
935
|
+
##
|
|
936
|
+
## @param search_term [String] The search term
|
|
937
|
+
## @param output [Array] Output array for errors
|
|
938
|
+
##
|
|
939
|
+
## @return [Array] Array of matched topics
|
|
940
|
+
##
|
|
941
|
+
def resolve_fuzzy_matches(search_term, output)
|
|
942
|
+
matches = find_topic(search_term)
|
|
943
|
+
|
|
944
|
+
return [] if matches.empty?
|
|
945
|
+
|
|
946
|
+
case Howzit.options[:multiple_matches]
|
|
947
|
+
when :first
|
|
948
|
+
[matches[0]]
|
|
949
|
+
when :best
|
|
950
|
+
[matches.sort_by { |a| [a.title.comp_distance(search_term), a.title.length] }.first]
|
|
951
|
+
when :all
|
|
952
|
+
matches
|
|
953
|
+
else
|
|
954
|
+
titles = matches.map(&:title)
|
|
955
|
+
res = Prompt.choose(titles, query: search_term)
|
|
956
|
+
old_matching = Howzit.options[:matching]
|
|
957
|
+
Howzit.options[:matching] = 'exact'
|
|
958
|
+
selected = res.flat_map { |title| find_topic(title) }
|
|
959
|
+
Howzit.options[:matching] = old_matching
|
|
960
|
+
selected
|
|
961
|
+
end
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
##
|
|
965
|
+
## Process collected topic matches and display output
|
|
966
|
+
##
|
|
967
|
+
## @param topic_matches [Array] Array of matched topics
|
|
968
|
+
## @param output [Array] Output array
|
|
969
|
+
##
|
|
970
|
+
def process_topic_matches(topic_matches, output)
|
|
971
|
+
if topic_matches.empty? && !Howzit.options[:show_all_on_error]
|
|
972
|
+
Util.show(output.join("\n"), { color: true, highlight: false, paginate: false, wrap: 0 })
|
|
973
|
+
Process.exit 1
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
if Howzit.options[:run]
|
|
977
|
+
Howzit.run_log = []
|
|
978
|
+
Howzit.multi_topic_run = topic_matches.length > 1
|
|
979
|
+
end
|
|
980
|
+
|
|
807
981
|
if !topic_matches.empty?
|
|
808
|
-
# If we found a match
|
|
809
982
|
topic_matches.map! { |topic| topic.is_a?(String) ? find_topic(topic)[0] : topic }
|
|
810
983
|
topic_matches.each { |topic_match| output.push(process_topic(topic_match, Howzit.options[:run], single: true)) }
|
|
811
984
|
else
|
|
812
|
-
# If there's no argument or no match found, output all
|
|
813
985
|
topics.each { |k| output.push(process_topic(k, false, single: false)) }
|
|
814
986
|
end
|
|
815
|
-
|
|
987
|
+
|
|
988
|
+
finalize_output(output)
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
##
|
|
992
|
+
## Finalize and display output with run summary if applicable
|
|
993
|
+
##
|
|
994
|
+
## @param output [Array] Output array
|
|
995
|
+
##
|
|
996
|
+
def finalize_output(output)
|
|
997
|
+
if Howzit.options[:run]
|
|
998
|
+
Howzit.options[:paginate] = false
|
|
999
|
+
summary = Howzit::RunReport.format
|
|
1000
|
+
output.push(summary) unless summary.empty?
|
|
1001
|
+
end
|
|
816
1002
|
Util.show(output.join("\n").strip, Howzit.options)
|
|
817
1003
|
end
|
|
818
1004
|
end
|
data/lib/howzit/prompt.rb
CHANGED
|
@@ -83,21 +83,24 @@ module Howzit
|
|
|
83
83
|
## number of options, anything
|
|
84
84
|
## else gets max height for
|
|
85
85
|
## terminal)
|
|
86
|
+
## @param query [String] The search term to display in prompt
|
|
86
87
|
##
|
|
87
88
|
## @return [Array] the selected results
|
|
88
89
|
##
|
|
89
|
-
def choose(matches, height: :auto)
|
|
90
|
-
return [] if
|
|
90
|
+
def choose(matches, height: :auto, query: nil)
|
|
91
|
+
return [] if matches.count.zero?
|
|
92
|
+
return matches if matches.count == 1
|
|
93
|
+
return [] unless $stdout.isatty
|
|
91
94
|
|
|
92
95
|
if Util.command_exist?('fzf')
|
|
93
96
|
height = height == :auto ? matches.count + 3 : TTY::Screen.rows
|
|
94
97
|
|
|
95
|
-
settings = fzf_options(height)
|
|
98
|
+
settings = fzf_options(height, query: query)
|
|
96
99
|
res = `echo #{Shellwords.escape(matches.join("\n"))} | fzf #{settings.join(' ')}`.strip
|
|
97
100
|
return fzf_result(res)
|
|
98
101
|
end
|
|
99
102
|
|
|
100
|
-
tty_menu(matches)
|
|
103
|
+
tty_menu(matches, query: query)
|
|
101
104
|
end
|
|
102
105
|
|
|
103
106
|
def fzf_result(res)
|
|
@@ -108,7 +111,12 @@ module Howzit
|
|
|
108
111
|
res.split(/\n/)
|
|
109
112
|
end
|
|
110
113
|
|
|
111
|
-
def fzf_options(height)
|
|
114
|
+
def fzf_options(height, query: nil)
|
|
115
|
+
prompt = if query
|
|
116
|
+
"Select a topic for \\`#{query}\\` > "
|
|
117
|
+
else
|
|
118
|
+
'Select a topic > '
|
|
119
|
+
end
|
|
112
120
|
[
|
|
113
121
|
'-0',
|
|
114
122
|
'-1',
|
|
@@ -116,7 +124,7 @@ module Howzit
|
|
|
116
124
|
"--height=#{height}",
|
|
117
125
|
'--header="Tab: add selection, ctrl-a/d: (de)select all, return: display/run"',
|
|
118
126
|
'--bind ctrl-a:select-all,ctrl-d:deselect-all,ctrl-t:toggle-all',
|
|
119
|
-
|
|
127
|
+
"--prompt=\"#{prompt}\"",
|
|
120
128
|
%(--preview="howzit --no-pager --header-format block --no-color --default --multiple first {}")
|
|
121
129
|
]
|
|
122
130
|
end
|
|
@@ -125,8 +133,9 @@ module Howzit
|
|
|
125
133
|
## Display a numeric menu on the TTY
|
|
126
134
|
##
|
|
127
135
|
## @param matches The matches from which to select
|
|
136
|
+
## @param query [String] The search term to display
|
|
128
137
|
##
|
|
129
|
-
def tty_menu(matches)
|
|
138
|
+
def tty_menu(matches, query: nil)
|
|
130
139
|
return matches if matches.count == 1
|
|
131
140
|
|
|
132
141
|
@stty_save = `stty -g`.chomp
|
|
@@ -136,6 +145,9 @@ module Howzit
|
|
|
136
145
|
exit
|
|
137
146
|
end
|
|
138
147
|
|
|
148
|
+
if query
|
|
149
|
+
puts "\nSelect a topic for `#{query}`:"
|
|
150
|
+
end
|
|
139
151
|
options_list(matches)
|
|
140
152
|
read_selection(matches)
|
|
141
153
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Howzit
|
|
4
|
+
# Formatter for task run summaries
|
|
5
|
+
module RunReport
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def reset
|
|
9
|
+
Howzit.run_log = []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def log(entry)
|
|
13
|
+
Howzit.run_log = [] if Howzit.run_log.nil?
|
|
14
|
+
Howzit.run_log << entry
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def entries
|
|
18
|
+
Howzit.run_log || []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def format
|
|
22
|
+
return '' if entries.empty?
|
|
23
|
+
|
|
24
|
+
lines = entries.map { |entry| format_line(entry, Howzit.multi_topic_run) }
|
|
25
|
+
lines.map! { |line| line.rstrip }
|
|
26
|
+
widths = lines.map { |line| line.uncolor.length }
|
|
27
|
+
width = widths.max
|
|
28
|
+
top = '=' * width
|
|
29
|
+
bottom = '-' * width
|
|
30
|
+
output_lines = [top] + lines + [bottom]
|
|
31
|
+
result = output_lines.join("\n")
|
|
32
|
+
result = result.gsub(/\n[ \t]+\n/, "\n")
|
|
33
|
+
result.gsub(/\n{2,}/, "\n")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def format_line(entry, prefix_topic)
|
|
37
|
+
bullet_start = '{mb}- [{x}'
|
|
38
|
+
bullet_end = '{mb}] {x}'
|
|
39
|
+
symbol = entry[:success] ? '{bg}✓{x}' : '{br}X{x}'
|
|
40
|
+
parts = []
|
|
41
|
+
parts << "#{bullet_start}#{symbol}#{bullet_end}"
|
|
42
|
+
parts << "{bl}#{entry[:topic]}{x}: " if prefix_topic && entry[:topic] && !entry[:topic].empty?
|
|
43
|
+
parts << "{by}#{entry[:task]}{x}"
|
|
44
|
+
unless entry[:success]
|
|
45
|
+
reason = entry[:exit_status] ? "exit code #{entry[:exit_status]}" : 'failed'
|
|
46
|
+
parts << " {br}(Failed: #{reason}){x}"
|
|
47
|
+
end
|
|
48
|
+
parts.join.c
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/howzit/stringutils.rb
CHANGED
|
@@ -183,7 +183,9 @@ module Howzit
|
|
|
183
183
|
|
|
184
184
|
# Just strip out color codes when requested
|
|
185
185
|
def uncolor
|
|
186
|
-
|
|
186
|
+
# force UTF-8 and remove invalid characters, then remove color codes
|
|
187
|
+
# and iTerm markers
|
|
188
|
+
gsub(Howzit::Color::COLORED_REGEXP, "").gsub(/\e\]1337;SetMark/, "")
|
|
187
189
|
end
|
|
188
190
|
|
|
189
191
|
# Wrap text at a specified width.
|
data/lib/howzit/task.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'English'
|
|
4
|
+
|
|
3
5
|
module Howzit
|
|
4
6
|
# Task object
|
|
5
7
|
class Task
|
|
6
|
-
attr_reader :type, :title, :action, :arguments, :parent, :optional, :default
|
|
8
|
+
attr_reader :type, :title, :action, :arguments, :parent, :optional, :default, :last_status
|
|
7
9
|
|
|
8
10
|
##
|
|
9
11
|
## Initialize a Task object
|
|
@@ -31,6 +33,7 @@ module Howzit
|
|
|
31
33
|
|
|
32
34
|
@optional = optional
|
|
33
35
|
@default = default
|
|
36
|
+
@last_status = nil
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
##
|
|
@@ -68,6 +71,7 @@ module Howzit
|
|
|
68
71
|
script.unlink
|
|
69
72
|
end
|
|
70
73
|
|
|
74
|
+
update_last_status(res ? 0 : 1)
|
|
71
75
|
res
|
|
72
76
|
end
|
|
73
77
|
|
|
@@ -86,6 +90,7 @@ module Howzit
|
|
|
86
90
|
Howzit.console.info("#{@prefix}{by}Running tasks from {bw}#{matches[0].title}{x}".c)
|
|
87
91
|
output.concat(matches[0].run(nested: true))
|
|
88
92
|
Howzit.console.info("{by}End include: #{matches[0].tasks.count} tasks{x}".c)
|
|
93
|
+
@last_status = nil
|
|
89
94
|
[output, matches[0].tasks.count]
|
|
90
95
|
end
|
|
91
96
|
|
|
@@ -96,7 +101,9 @@ module Howzit
|
|
|
96
101
|
title = Howzit.options[:show_all_code] ? @action : @title
|
|
97
102
|
Howzit.console.info("#{@prefix}{bg}Running {bw}#{title}{x}".c)
|
|
98
103
|
ENV['HOWZIT_SCRIPTS'] = File.expand_path('~/.config/howzit/scripts')
|
|
99
|
-
system(@action)
|
|
104
|
+
res = system(@action)
|
|
105
|
+
update_last_status(res ? 0 : 1)
|
|
106
|
+
res
|
|
100
107
|
end
|
|
101
108
|
|
|
102
109
|
##
|
|
@@ -106,6 +113,7 @@ module Howzit
|
|
|
106
113
|
title = Howzit.options[:show_all_code] ? @action : @title
|
|
107
114
|
Howzit.console.info("#{@prefix}{bg}Copied {bw}#{title}{bg} to clipboard{x}".c)
|
|
108
115
|
Util.os_copy(@action)
|
|
116
|
+
@last_status = 0
|
|
109
117
|
true
|
|
110
118
|
end
|
|
111
119
|
|
|
@@ -127,12 +135,23 @@ module Howzit
|
|
|
127
135
|
run_copy
|
|
128
136
|
when :open
|
|
129
137
|
Util.os_open(@action)
|
|
138
|
+
@last_status = 0
|
|
139
|
+
true
|
|
130
140
|
end
|
|
131
141
|
end
|
|
132
142
|
|
|
133
143
|
[output, tasks, res]
|
|
134
144
|
end
|
|
135
145
|
|
|
146
|
+
def update_last_status(default = nil)
|
|
147
|
+
status = if defined?($CHILD_STATUS) && $CHILD_STATUS
|
|
148
|
+
$CHILD_STATUS.exitstatus
|
|
149
|
+
else
|
|
150
|
+
default
|
|
151
|
+
end
|
|
152
|
+
@last_status = status
|
|
153
|
+
end
|
|
154
|
+
|
|
136
155
|
##
|
|
137
156
|
## Output terminal-formatted list item
|
|
138
157
|
##
|
data/lib/howzit/topic.rb
CHANGED
|
@@ -104,6 +104,8 @@ module Howzit
|
|
|
104
104
|
|
|
105
105
|
break unless Howzit.options[:force]
|
|
106
106
|
end
|
|
107
|
+
|
|
108
|
+
log_task_result(task, success)
|
|
107
109
|
end
|
|
108
110
|
|
|
109
111
|
total = "{bw}#{@results[:total]}{by} #{@results[:total] == 1 ? 'task' : 'tasks'}".c
|
|
@@ -119,7 +121,7 @@ module Howzit
|
|
|
119
121
|
Howzit.console.warn "{r}--run: No {br}@directive{xr} found in {bw}#{@title}{x}".c
|
|
120
122
|
end
|
|
121
123
|
|
|
122
|
-
output.push(@results[:message]) if Howzit.options[:log_level] < 2 && !nested
|
|
124
|
+
output.push(@results[:message]) if Howzit.options[:log_level] < 2 && !nested && !Howzit.options[:run]
|
|
123
125
|
|
|
124
126
|
puts TTY::Box.frame("{bw}#{@postreqs.join("\n\n").wrap(cols - 4)}{x}".c, width: cols) unless @postreqs.empty?
|
|
125
127
|
|
|
@@ -324,6 +326,27 @@ module Howzit
|
|
|
324
326
|
##
|
|
325
327
|
## @return [Array] array of Task objects
|
|
326
328
|
##
|
|
329
|
+
def log_task_result(task, success)
|
|
330
|
+
return unless Howzit.options[:run]
|
|
331
|
+
return if task.type == :include
|
|
332
|
+
|
|
333
|
+
Howzit.run_log ||= []
|
|
334
|
+
|
|
335
|
+
title = (task.title || '').strip
|
|
336
|
+
if title.empty?
|
|
337
|
+
action = (task.action || '').strip
|
|
338
|
+
title = action.split(/\n/).first.to_s.strip
|
|
339
|
+
end
|
|
340
|
+
title = task.type.to_s.capitalize if title.nil? || title.empty?
|
|
341
|
+
|
|
342
|
+
Howzit.run_log << {
|
|
343
|
+
topic: @title,
|
|
344
|
+
task: title,
|
|
345
|
+
success: success ? true : false,
|
|
346
|
+
exit_status: task.last_status
|
|
347
|
+
}
|
|
348
|
+
end
|
|
349
|
+
|
|
327
350
|
def gather_tasks
|
|
328
351
|
runnable = []
|
|
329
352
|
@prereqs = @content.scan(/(?<=@before\n).*?(?=\n@end)/im).map(&:strip)
|
data/lib/howzit/util.rb
CHANGED
|
@@ -145,7 +145,11 @@ module Howzit
|
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
read_io.close
|
|
148
|
-
|
|
148
|
+
begin
|
|
149
|
+
write_io.write(text)
|
|
150
|
+
rescue Errno::EPIPE
|
|
151
|
+
# User quit pager before we finished writing, ignore
|
|
152
|
+
end
|
|
149
153
|
write_io.close
|
|
150
154
|
|
|
151
155
|
_, status = Process.waitpid2(pid)
|
data/lib/howzit/version.rb
CHANGED
data/lib/howzit.rb
CHANGED
|
@@ -41,6 +41,7 @@ require_relative 'howzit/config'
|
|
|
41
41
|
require_relative 'howzit/task'
|
|
42
42
|
require_relative 'howzit/topic'
|
|
43
43
|
require_relative 'howzit/buildnote'
|
|
44
|
+
require_relative 'howzit/run_report'
|
|
44
45
|
|
|
45
46
|
require 'tty/screen'
|
|
46
47
|
require 'tty/box'
|
|
@@ -49,7 +50,7 @@ require 'tty/box'
|
|
|
49
50
|
# Main module for howzit
|
|
50
51
|
module Howzit
|
|
51
52
|
class << self
|
|
52
|
-
attr_accessor :arguments, :named_arguments, :cli_args
|
|
53
|
+
attr_accessor :arguments, :named_arguments, :cli_args, :run_log, :multi_topic_run
|
|
53
54
|
|
|
54
55
|
##
|
|
55
56
|
## Holds a Configuration object with methods and a @settings hash
|
|
@@ -88,10 +89,18 @@ module Howzit
|
|
|
88
89
|
@console ||= Howzit::ConsoleLogger.new(options[:log_level])
|
|
89
90
|
end
|
|
90
91
|
|
|
92
|
+
def run_log
|
|
93
|
+
@run_log ||= []
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def multi_topic_run
|
|
97
|
+
@multi_topic_run ||= false
|
|
98
|
+
end
|
|
99
|
+
|
|
91
100
|
def has_read_upstream
|
|
92
101
|
@has_read_upstream ||= false
|
|
93
102
|
end
|
|
94
103
|
|
|
95
|
-
attr_writer :has_read_upstream
|
|
104
|
+
attr_writer :has_read_upstream, :run_log
|
|
96
105
|
end
|
|
97
106
|
end
|