howzit 2.1.39 → 2.1.41
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 +29 -0
- data/bin/howzit +6 -0
- data/fish/functions/howzit_command_not_found.fish +15 -0
- data/lib/howzit/buildnote.rb +9 -0
- data/lib/howzit/condition_evaluator.rb +11 -0
- data/lib/howzit/topic.rb +56 -32
- data/lib/howzit/util.rb +12 -1
- data/lib/howzit/version.rb +1 -1
- data/lib/howzit.rb +1 -1
- data/spec/condition_evaluator_spec.rb +24 -0
- data/spec/sequential_conditional_spec.rb +49 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/topic_positional_snapshot_spec.rb +33 -0
- data/spec/topic_spec.rb +1 -1
- data/zsh/howzit-command-not-found.zsh +22 -0
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 30837683e82b813fd1fd8ff3e2df3177cbee3c735fc5afb2cb3394f08fb341eb
|
|
4
|
+
data.tar.gz: 4a0113424a3e36169a0f7a66f31b6dcc3adc24785d9a1a54829db7d9d907c585
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c0245bdb6e801d1efb96e800c04a7ac1177a589e9d25753322bb70a6690ce22b738fdfdc12956aae216565e93ea23b7cdcc14b6f278937e329b116b8d40b5cd
|
|
7
|
+
data.tar.gz: 986c7c270fc4d8aa21c395fab7a1edaa5669c4a72e6160dede78e45c9c9b6065e69ca47c6c4253b1eae1ec2909d4e31224dee333ba74e6b392e937fe1f7f25ae
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
### 2.1.41
|
|
2
|
+
|
|
3
|
+
2026-05-10 08:18
|
|
4
|
+
|
|
5
|
+
#### NEW
|
|
6
|
+
|
|
7
|
+
- `-z name` and `-n name` in `@if`/`@unless` conditions for shell-style empty and non-empty checks on named and positional variables.
|
|
8
|
+
|
|
9
|
+
#### IMPROVED
|
|
10
|
+
|
|
11
|
+
- `Util.show` strips safely when the default external encoding rejects multibyte content (fewer crashes under strict ASCII locale when piping or highlighting).
|
|
12
|
+
|
|
13
|
+
#### FIXED
|
|
14
|
+
|
|
15
|
+
- @set_var and @log_level inside @if/@else/@elsif so only the active branch runs; the previous behavior ran every branch and could overwrite variables set in another branch (for example ONLY_RUN set in @if then replaced by @else).
|
|
16
|
+
- Topic title parameters from arguments after `--` so they no longer shift when another topic is parsed first with `@include(...[args])` (CLI positional snapshot kept separate from mutable Howzit.arguments during load).
|
|
17
|
+
|
|
18
|
+
### 2.1.40
|
|
19
|
+
|
|
20
|
+
2026-02-04 07:53
|
|
21
|
+
|
|
22
|
+
#### NEW
|
|
23
|
+
|
|
24
|
+
- Added a --test-search flag that exits 0 when a search term matches at least one topic and 1 otherwise, with no normal output, allowing shells and scripts to probe for howzit topics programmatically
|
|
25
|
+
|
|
26
|
+
#### IMPROVED
|
|
27
|
+
|
|
28
|
+
- Build note processing in test-search mode now short-circuits without auto-creating buildnotes and treats the absence of any note file as a clean non-match instead of an error
|
|
29
|
+
|
|
1
30
|
### 2.1.39
|
|
2
31
|
|
|
3
32
|
2026-01-25 07:39
|
data/bin/howzit
CHANGED
|
@@ -9,6 +9,8 @@ Howzit::Color.coloring = $stdout.isatty
|
|
|
9
9
|
parts = Shellwords.shelljoin(ARGV).split(/ -- /)
|
|
10
10
|
args = parts[0] ? Shellwords.shellsplit(parts[0]) : []
|
|
11
11
|
Howzit.arguments = parts[1] ? Shellwords.shellsplit(parts[1]) : []
|
|
12
|
+
# Immutable snapshot for topic title (param): binding — gather_tasks/@include mutates Howzit.arguments while parsing earlier topics
|
|
13
|
+
Howzit.cli_topic_positional_args = Howzit.arguments&.dup || []
|
|
12
14
|
Howzit.named_arguments = {}
|
|
13
15
|
|
|
14
16
|
OptionParser.new do |opts|
|
|
@@ -31,6 +33,10 @@ OptionParser.new do |opts|
|
|
|
31
33
|
|
|
32
34
|
opts.on('-f', '--force', 'Continue executing after an error') { Howzit.options[:force] = true }
|
|
33
35
|
|
|
36
|
+
opts.on('--test-search TERM', 'Exit 0 if TERM matches any topic, 1 otherwise (no output)') do |term|
|
|
37
|
+
Howzit.options[:test_search] = term
|
|
38
|
+
end
|
|
39
|
+
|
|
34
40
|
opts.on('-m', '--matching TYPE', MATCHING_OPTIONS,
|
|
35
41
|
'Topics matching type', "(#{MATCHING_OPTIONS.join(', ').sub(/#{Howzit.options[:matching]}/, "*#{Howzit.options[:matching]}")})") do |c|
|
|
36
42
|
Howzit.options[:matching] = c
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function fish_command_not_found --description "Use howzit topics for unknown commands when available"
|
|
2
|
+
set cmd $argv[1]
|
|
3
|
+
set args $argv[2..-1]
|
|
4
|
+
|
|
5
|
+
# If a howzit topic matches the command name, run it instead
|
|
6
|
+
if howzit --test-search $cmd >/dev/null 2>&1
|
|
7
|
+
howzit -r $cmd $args
|
|
8
|
+
return $status
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Fall back to the default unknown-command message
|
|
12
|
+
echo "fish: Unknown command '$cmd'"
|
|
13
|
+
return 127
|
|
14
|
+
end
|
|
15
|
+
|
data/lib/howzit/buildnote.rb
CHANGED
|
@@ -1126,6 +1126,15 @@ module Howzit
|
|
|
1126
1126
|
Howzit.multi_topic_run = false
|
|
1127
1127
|
end
|
|
1128
1128
|
|
|
1129
|
+
# Test-search mode: do not auto-create a build note; just check for a match
|
|
1130
|
+
if Howzit.options[:test_search]
|
|
1131
|
+
term = Howzit.options[:test_search]
|
|
1132
|
+
# If no note file can be found at all, treat as no match
|
|
1133
|
+
Process.exit(1) unless note_file
|
|
1134
|
+
matches = find_topic(term)
|
|
1135
|
+
Process.exit(matches.any? ? 0 : 1)
|
|
1136
|
+
end
|
|
1137
|
+
|
|
1129
1138
|
unless note_file
|
|
1130
1139
|
Process.exit 0 if Howzit.options[:list_runnable_titles] || Howzit.options[:list_topic_titles]
|
|
1131
1140
|
|
|
@@ -35,6 +35,17 @@ module Howzit
|
|
|
35
35
|
## Evaluate a single condition (without negation)
|
|
36
36
|
##
|
|
37
37
|
def evaluate_condition(condition, context)
|
|
38
|
+
# Shell-style empty / non-empty tests (bash -z / -n)
|
|
39
|
+
if (match = condition.match(/^-z\s+(.+)$/i))
|
|
40
|
+
name = match[1].strip
|
|
41
|
+
val = get_value(name, context)
|
|
42
|
+
return val.nil? || val.to_s.strip.empty?
|
|
43
|
+
elsif (match = condition.match(/^-n\s+(.+)$/i))
|
|
44
|
+
name = match[1].strip
|
|
45
|
+
val = get_value(name, context)
|
|
46
|
+
return !val.nil? && !val.to_s.strip.empty?
|
|
47
|
+
end
|
|
48
|
+
|
|
38
49
|
# Handle special conditions FIRST to avoid false matches with comparison patterns
|
|
39
50
|
# Check file contents before other patterns since it has arguments and operators
|
|
40
51
|
if condition =~ /^file\s+contents\s+(.+?)\s+(\*\*=|\*=|\^=|\$=|==|!=|=~)\s*(.+)$/i
|
data/lib/howzit/topic.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Howzit
|
|
|
25
25
|
@named_args = {}
|
|
26
26
|
@metadata = metadata
|
|
27
27
|
@source_file = source_file
|
|
28
|
-
arguments
|
|
28
|
+
arguments(from_cli_snapshot: true)
|
|
29
29
|
|
|
30
30
|
@directives = parse_directives_with_conditionals
|
|
31
31
|
@tasks = gather_tasks
|
|
@@ -33,10 +33,23 @@ module Howzit
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# Get named arguments from title
|
|
36
|
-
|
|
36
|
+
# from_cli_snapshot: use Howzit.cli_topic_positional_args (set once from argv after ` -- `) so earlier topics' gather_tasks
|
|
37
|
+
# cannot clobber positional binding. Re-entrant calls (e.g. @include with [a,b]) pass false to use live Howzit.arguments.
|
|
38
|
+
def arguments(from_cli_snapshot: false)
|
|
37
39
|
@arg_definitions = []
|
|
38
40
|
return unless @title =~ /\(.*?\) *$/
|
|
39
41
|
|
|
42
|
+
positional = if from_cli_snapshot
|
|
43
|
+
# Specs / non-CLI: leave unset to keep using Howzit.arguments
|
|
44
|
+
if Howzit.cli_topic_positional_args.nil?
|
|
45
|
+
Howzit.arguments || []
|
|
46
|
+
else
|
|
47
|
+
Howzit.cli_topic_positional_args
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
Howzit.arguments || []
|
|
51
|
+
end
|
|
52
|
+
|
|
40
53
|
a = @title.match(/\((?<args>.*?)\) *$/)
|
|
41
54
|
args = a['args'].split(/ *, */).each(&:strip)
|
|
42
55
|
|
|
@@ -45,8 +58,8 @@ module Howzit
|
|
|
45
58
|
# Store original definition for display purposes
|
|
46
59
|
@arg_definitions << (default ? "#{arg_name}:#{default}" : arg_name)
|
|
47
60
|
|
|
48
|
-
@named_args[arg_name] = if
|
|
49
|
-
|
|
61
|
+
@named_args[arg_name] = if positional && positional.count >= idx + 1
|
|
62
|
+
positional[idx]
|
|
50
63
|
else
|
|
51
64
|
default
|
|
52
65
|
end
|
|
@@ -642,6 +655,7 @@ module Howzit
|
|
|
642
655
|
if line =~ /^@log_level\s*\(([^)]+)\)\s*$/i
|
|
643
656
|
log_level = Regexp.last_match(1).strip
|
|
644
657
|
conditional_path = conditional_stack.dup
|
|
658
|
+
conditional_path << current_branch_index if current_branch_index
|
|
645
659
|
directives << Howzit::Directive.new(
|
|
646
660
|
type: :log_level,
|
|
647
661
|
log_level_value: log_level,
|
|
@@ -664,6 +678,7 @@ module Howzit
|
|
|
664
678
|
# Remove quotes from value if present (handles both single and double quotes)
|
|
665
679
|
var_value = Regexp.last_match(1) if var_value =~ /^["'](.+)["']$/
|
|
666
680
|
conditional_path = conditional_stack.dup
|
|
681
|
+
conditional_path << current_branch_index if current_branch_index
|
|
667
682
|
directives << Howzit::Directive.new(
|
|
668
683
|
type: :set_var,
|
|
669
684
|
var_name: var_name,
|
|
@@ -818,12 +833,16 @@ module Howzit
|
|
|
818
833
|
|
|
819
834
|
# Handle @log_level directive (before task check)
|
|
820
835
|
if directive.log_level?
|
|
836
|
+
next unless directive_in_active_branch?(directive, conditional_state)
|
|
837
|
+
|
|
821
838
|
current_log_level = directive.log_level_value
|
|
822
839
|
next
|
|
823
840
|
end
|
|
824
841
|
|
|
825
842
|
# Handle @set_var directive (before task check)
|
|
826
843
|
if directive.set_var?
|
|
844
|
+
next unless directive_in_active_branch?(directive, conditional_state)
|
|
845
|
+
|
|
827
846
|
# Set the variable in named_arguments
|
|
828
847
|
Howzit.named_arguments ||= {}
|
|
829
848
|
value = directive.var_value
|
|
@@ -856,34 +875,7 @@ module Howzit
|
|
|
856
875
|
# Handle task directives
|
|
857
876
|
next unless directive.task?
|
|
858
877
|
|
|
859
|
-
|
|
860
|
-
should_execute = true
|
|
861
|
-
|
|
862
|
-
# If path ends with an @elsif/@else, skip the parent @if index
|
|
863
|
-
# (the index right before the elsif/else in the path)
|
|
864
|
-
path_to_check = directive.conditional_path.dup
|
|
865
|
-
if path_to_check.length >= 2
|
|
866
|
-
last_idx = path_to_check.last
|
|
867
|
-
last_state = conditional_state[last_idx]
|
|
868
|
-
if last_state && %w[elsif else].include?(last_state[:directive_type])
|
|
869
|
-
# Skip the parent @if index (the one before the elsif/else)
|
|
870
|
-
parent_if_idx = path_to_check[path_to_check.length - 2]
|
|
871
|
-
parent_if_state = conditional_state[parent_if_idx]
|
|
872
|
-
if parent_if_state && %w[if unless].include?(parent_if_state[:directive_type])
|
|
873
|
-
path_to_check.delete(parent_if_idx)
|
|
874
|
-
end
|
|
875
|
-
end
|
|
876
|
-
end
|
|
877
|
-
|
|
878
|
-
path_to_check.each do |cond_idx|
|
|
879
|
-
cond_state = conditional_state[cond_idx]
|
|
880
|
-
if cond_state.nil? || !cond_state[:evaluated] || !cond_state[:result]
|
|
881
|
-
should_execute = false
|
|
882
|
-
break
|
|
883
|
-
end
|
|
884
|
-
end
|
|
885
|
-
|
|
886
|
-
next unless should_execute
|
|
878
|
+
next unless directive_in_active_branch?(directive, conditional_state)
|
|
887
879
|
|
|
888
880
|
# Convert directive to task
|
|
889
881
|
task = directive.to_task(self, current_log_level: current_log_level)
|
|
@@ -939,6 +931,38 @@ module Howzit
|
|
|
939
931
|
output
|
|
940
932
|
end
|
|
941
933
|
|
|
934
|
+
##
|
|
935
|
+
## Whether a directive nested under @if/@unless/@elsif/@else should run (same rules as tasks).
|
|
936
|
+
##
|
|
937
|
+
def directive_in_active_branch?(directive, conditional_state)
|
|
938
|
+
path = directive.conditional_path || []
|
|
939
|
+
return true if path.empty?
|
|
940
|
+
|
|
941
|
+
should_execute = true
|
|
942
|
+
path_to_check = path.dup
|
|
943
|
+
if path_to_check.length >= 2
|
|
944
|
+
last_idx = path_to_check.last
|
|
945
|
+
last_state = conditional_state[last_idx]
|
|
946
|
+
if last_state && %w[elsif else].include?(last_state[:directive_type])
|
|
947
|
+
parent_if_idx = path_to_check[path_to_check.length - 2]
|
|
948
|
+
parent_if_state = conditional_state[parent_if_idx]
|
|
949
|
+
if parent_if_state && %w[if unless].include?(parent_if_state[:directive_type])
|
|
950
|
+
path_to_check.delete(parent_if_idx)
|
|
951
|
+
end
|
|
952
|
+
end
|
|
953
|
+
end
|
|
954
|
+
|
|
955
|
+
path_to_check.each do |cond_idx|
|
|
956
|
+
cond_state = conditional_state[cond_idx]
|
|
957
|
+
if cond_state.nil? || !cond_state[:evaluated] || !cond_state[:result]
|
|
958
|
+
should_execute = false
|
|
959
|
+
break
|
|
960
|
+
end
|
|
961
|
+
end
|
|
962
|
+
|
|
963
|
+
should_execute
|
|
964
|
+
end
|
|
965
|
+
|
|
942
966
|
##
|
|
943
967
|
## Find the index of the matching @if/@unless for an @elsif/@else/@end
|
|
944
968
|
##
|
data/lib/howzit/util.rb
CHANGED
|
@@ -161,6 +161,14 @@ module Howzit
|
|
|
161
161
|
status.success?
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
+
# Strip safely when external encoding rejects multibyte content (e.g. US-ASCII default).
|
|
165
|
+
def safe_strip(str)
|
|
166
|
+
s = str.to_s
|
|
167
|
+
s.strip
|
|
168
|
+
rescue Encoding::CompatibilityError
|
|
169
|
+
s.encode('UTF-8', invalid: :replace, undef: :replace).strip
|
|
170
|
+
end
|
|
171
|
+
|
|
164
172
|
# print output to terminal
|
|
165
173
|
def show(string, opts = {})
|
|
166
174
|
options = {
|
|
@@ -180,7 +188,10 @@ module Howzit
|
|
|
180
188
|
pipes = "|#{hl}" if hl
|
|
181
189
|
end
|
|
182
190
|
|
|
183
|
-
|
|
191
|
+
string = safe_strip(string)
|
|
192
|
+
|
|
193
|
+
raw_output = `echo #{Shellwords.escape(string)}#{pipes}`
|
|
194
|
+
output = safe_strip(raw_output)
|
|
184
195
|
|
|
185
196
|
if options[:paginate] && Howzit.options[:paginate]
|
|
186
197
|
page(output)
|
data/lib/howzit/version.rb
CHANGED
data/lib/howzit.rb
CHANGED
|
@@ -60,7 +60,7 @@ require 'tty/box'
|
|
|
60
60
|
# Main module for howzit
|
|
61
61
|
module Howzit
|
|
62
62
|
class << self
|
|
63
|
-
attr_accessor :arguments, :named_arguments, :cli_args, :run_log, :multi_topic_run
|
|
63
|
+
attr_accessor :arguments, :named_arguments, :cli_topic_positional_args, :cli_args, :run_log, :multi_topic_run
|
|
64
64
|
|
|
65
65
|
##
|
|
66
66
|
## Holds a Configuration object with methods and a @settings hash
|
|
@@ -122,6 +122,30 @@ describe Howzit::ConditionEvaluator do
|
|
|
122
122
|
expect(described_class.evaluate('${env} == "production"', {})).to be true
|
|
123
123
|
expect(described_class.evaluate('${var} == "other"', {})).to be false
|
|
124
124
|
end
|
|
125
|
+
|
|
126
|
+
it 'evaluates -z (empty) for named arguments' do
|
|
127
|
+
Howzit.named_arguments = { 'option' => '', other: 'x' }
|
|
128
|
+
expect(described_class.evaluate('-z option', {})).to be true
|
|
129
|
+
expect(described_class.evaluate('-z other', {})).to be false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'evaluates -n (non-empty) for named arguments' do
|
|
133
|
+
Howzit.named_arguments = { 'option' => '', other: 'x' }
|
|
134
|
+
expect(described_class.evaluate('-n option', {})).to be false
|
|
135
|
+
expect(described_class.evaluate('-n other', {})).to be true
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
it 'treats undefined named argument as empty for -z / -n' do
|
|
139
|
+
Howzit.named_arguments = {}
|
|
140
|
+
expect(described_class.evaluate('-z missing', {})).to be true
|
|
141
|
+
expect(described_class.evaluate('-n missing', {})).to be false
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'supports not -z and not -n' do
|
|
145
|
+
Howzit.named_arguments = { opt: 'yes' }
|
|
146
|
+
expect(described_class.evaluate('not -z opt', {})).to be true
|
|
147
|
+
expect(described_class.evaluate('not -n opt', {})).to be false
|
|
148
|
+
end
|
|
125
149
|
end
|
|
126
150
|
|
|
127
151
|
context 'with metadata' do
|
|
@@ -317,4 +317,53 @@ describe 'Sequential Conditional Evaluation' do
|
|
|
317
317
|
expect(Howzit.named_arguments['ALL']).to eq('123')
|
|
318
318
|
end
|
|
319
319
|
end
|
|
320
|
+
|
|
321
|
+
describe '@set_var under @if/@else' do
|
|
322
|
+
let(:branch_note) do
|
|
323
|
+
<<~EONOTE
|
|
324
|
+
# Test
|
|
325
|
+
|
|
326
|
+
## Branch Topic (flag)
|
|
327
|
+
|
|
328
|
+
@if -n flag
|
|
329
|
+
@set_var(RESULT, "yes")
|
|
330
|
+
@else
|
|
331
|
+
@set_var(RESULT, "no")
|
|
332
|
+
@end
|
|
333
|
+
|
|
334
|
+
```run Echo
|
|
335
|
+
#!/bin/bash
|
|
336
|
+
echo ok
|
|
337
|
+
```
|
|
338
|
+
EONOTE
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
it 'only runs @set_var from the active branch when @if is true' do
|
|
342
|
+
File.open('builda.md', 'w') { |f| f.puts branch_note }
|
|
343
|
+
Howzit.arguments = ['present']
|
|
344
|
+
Howzit.cli_topic_positional_args = ['present']
|
|
345
|
+
Howzit.named_arguments = {}
|
|
346
|
+
Howzit.instance_variable_set(:@buildnote, nil)
|
|
347
|
+
topic = Howzit.buildnote('builda.md').find_topic('Branch Topic')[0]
|
|
348
|
+
allow(Howzit::Prompt).to receive(:yn).and_return(true)
|
|
349
|
+
|
|
350
|
+
topic.run
|
|
351
|
+
|
|
352
|
+
expect(Howzit.named_arguments['RESULT']).to eq('yes')
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
it 'only runs @set_var from the active branch when @else is taken' do
|
|
356
|
+
File.open('builda.md', 'w') { |f| f.puts branch_note }
|
|
357
|
+
Howzit.arguments = []
|
|
358
|
+
Howzit.cli_topic_positional_args = []
|
|
359
|
+
Howzit.named_arguments = {}
|
|
360
|
+
Howzit.instance_variable_set(:@buildnote, nil)
|
|
361
|
+
topic = Howzit.buildnote('builda.md').find_topic('Branch Topic')[0]
|
|
362
|
+
allow(Howzit::Prompt).to receive(:yn).and_return(true)
|
|
363
|
+
|
|
364
|
+
topic.run
|
|
365
|
+
|
|
366
|
+
expect(Howzit.named_arguments['RESULT']).to eq('no')
|
|
367
|
+
end
|
|
368
|
+
end
|
|
320
369
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -18,11 +18,19 @@ RSpec.configure do |c|
|
|
|
18
18
|
save_buildnote
|
|
19
19
|
# Reset buildnote cache to ensure fresh instance with updated file
|
|
20
20
|
Howzit.instance_variable_set(:@buildnote, nil)
|
|
21
|
+
Howzit.arguments = []
|
|
22
|
+
Howzit.cli_topic_positional_args = nil
|
|
23
|
+
Howzit.named_arguments = {}
|
|
21
24
|
Howzit.options[:include_upstream] = false
|
|
22
25
|
Howzit.options[:stack] = false
|
|
23
26
|
Howzit.options[:default] = true
|
|
24
27
|
Howzit.options[:matching] = 'partial'
|
|
25
28
|
Howzit.options[:multiple_matches] = 'choose'
|
|
29
|
+
# Point singleton at test fixture (repo may also contain buildnotes.md, etc.)
|
|
30
|
+
Howzit.instance_variable_set(
|
|
31
|
+
:@buildnote,
|
|
32
|
+
Howzit::BuildNote.new(file: File.expand_path('builda.md'))
|
|
33
|
+
)
|
|
26
34
|
@hz = Howzit.buildnote
|
|
27
35
|
end
|
|
28
36
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
describe 'Topic title positional args vs gather_tasks' do
|
|
7
|
+
it 'uses CLI positional snapshot so a prior topic @include […] does not shift later topic params' do
|
|
8
|
+
Howzit.arguments = ['from_cli']
|
|
9
|
+
Howzit.cli_topic_positional_args = ['from_cli']
|
|
10
|
+
|
|
11
|
+
Tempfile.create(['howzit-pos', '.md']) do |f|
|
|
12
|
+
f.write(<<~MD)
|
|
13
|
+
defined: snap
|
|
14
|
+
|
|
15
|
+
# Note
|
|
16
|
+
|
|
17
|
+
## First Topic
|
|
18
|
+
|
|
19
|
+
@include(DoesNotNeedToExist[y])
|
|
20
|
+
|
|
21
|
+
## Widget Topic (only:falafel)
|
|
22
|
+
|
|
23
|
+
ok
|
|
24
|
+
MD
|
|
25
|
+
f.flush
|
|
26
|
+
|
|
27
|
+
note = Howzit::BuildNote.new(file: f.path)
|
|
28
|
+
widget = note.topics.find { |t| t.title == 'Widget Topic' }
|
|
29
|
+
expect(widget).not_to be_nil
|
|
30
|
+
expect(widget.named_args['only']).to eq('from_cli')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/spec/topic_spec.rb
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# howzit command-not-found handler for zsh
|
|
2
|
+
#
|
|
3
|
+
# When an unknown command is entered, this handler will check to see
|
|
4
|
+
# if there is a howzit topic matching the command name. If so, it
|
|
5
|
+
# runs `howzit -r <command> [args...]`. Otherwise, it falls back to
|
|
6
|
+
# the standard "command not found" behavior.
|
|
7
|
+
|
|
8
|
+
command_not_found_handler() {
|
|
9
|
+
local cmd="$1"
|
|
10
|
+
shift
|
|
11
|
+
|
|
12
|
+
# If a howzit topic matches the command name, run it instead
|
|
13
|
+
if howzit --test-search "$cmd" >/dev/null 2>&1; then
|
|
14
|
+
howzit -r "$cmd" "$@"
|
|
15
|
+
return $?
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
# Fall back to the default unknown-command message
|
|
19
|
+
print "zsh: command not found: $cmd" >&2
|
|
20
|
+
return 127
|
|
21
|
+
}
|
|
22
|
+
|
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: 2.1.
|
|
4
|
+
version: 2.1.41
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brett Terpstra
|
|
@@ -294,6 +294,7 @@ files:
|
|
|
294
294
|
- fish/completions/fisher.fish
|
|
295
295
|
- fish/completions/howzit.fish
|
|
296
296
|
- fish/functions/bld.fish
|
|
297
|
+
- fish/functions/howzit_command_not_found.fish
|
|
297
298
|
- howzit.gemspec
|
|
298
299
|
- lib/howzit.rb
|
|
299
300
|
- lib/howzit/buildnote.rb
|
|
@@ -330,10 +331,12 @@ files:
|
|
|
330
331
|
- spec/stack_mode_spec.rb
|
|
331
332
|
- spec/stringutils_spec.rb
|
|
332
333
|
- spec/task_spec.rb
|
|
334
|
+
- spec/topic_positional_snapshot_spec.rb
|
|
333
335
|
- spec/topic_spec.rb
|
|
334
336
|
- spec/util_spec.rb
|
|
335
337
|
- src/_README.md
|
|
336
338
|
- update_readmes.rb
|
|
339
|
+
- zsh/howzit-command-not-found.zsh
|
|
337
340
|
homepage: https://github.com/ttscoff/howzit
|
|
338
341
|
licenses:
|
|
339
342
|
- MIT
|
|
@@ -352,7 +355,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
352
355
|
- !ruby/object:Gem::Version
|
|
353
356
|
version: '0'
|
|
354
357
|
requirements: []
|
|
355
|
-
rubygems_version:
|
|
358
|
+
rubygems_version: 3.6.7
|
|
356
359
|
specification_version: 4
|
|
357
360
|
summary: Provides a way to access Markdown project notes by topic with query capabilities
|
|
358
361
|
and the ability to execute the tasks it describes.
|
|
@@ -373,5 +376,6 @@ test_files:
|
|
|
373
376
|
- spec/stack_mode_spec.rb
|
|
374
377
|
- spec/stringutils_spec.rb
|
|
375
378
|
- spec/task_spec.rb
|
|
379
|
+
- spec/topic_positional_snapshot_spec.rb
|
|
376
380
|
- spec/topic_spec.rb
|
|
377
381
|
- spec/util_spec.rb
|