howzit 2.1.28 → 2.1.29

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.
@@ -0,0 +1,307 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
5
+ module Howzit
6
+ # Condition Evaluator module
7
+ # Handles evaluation of @if/@unless conditions
8
+ module ConditionEvaluator
9
+ class << self
10
+ ##
11
+ ## Evaluate a condition expression
12
+ ##
13
+ ## @param condition [String] The condition to evaluate
14
+ ## @param context [Hash] Context with metadata, arguments, etc.
15
+ ##
16
+ ## @return [Boolean] Result of condition evaluation
17
+ ##
18
+ def evaluate(condition, context = {})
19
+ condition = condition.strip
20
+
21
+ # Handle negation with 'not' or '!'
22
+ negated = false
23
+ if condition =~ /^(not\s+|!)/
24
+ negated = true
25
+ condition = condition.sub(/^(not\s+|!)/, '').strip
26
+ end
27
+
28
+ result = evaluate_condition(condition, context)
29
+ negated ? !result : result
30
+ end
31
+
32
+ private
33
+
34
+ ##
35
+ ## Evaluate a single condition (without negation)
36
+ ##
37
+ def evaluate_condition(condition, context)
38
+ # Handle special conditions FIRST to avoid false matches with comparison patterns
39
+ # Check file contents before other patterns since it has arguments and operators
40
+ if condition =~ /^file\s+contents\s+(.+?)\s+(\*\*=|\*=|\^=|\$=|==|!=|=~)\s*(.+)$/i
41
+ return evaluate_file_contents(condition, context)
42
+ # Check file/dir/topic exists before other patterns since they have arguments
43
+ elsif condition =~ /^(file\s+exists|dir\s+exists|topic\s+exists)\s+(.+)$/i
44
+ return evaluate_special(condition, context)
45
+ elsif condition =~ /^(git\s+dirty|git\s+clean)$/i
46
+ return evaluate_special(condition, context)
47
+ end
48
+
49
+ # Handle =~ regex comparisons separately (before string == to avoid conflicts)
50
+ if (match = condition.match(%r{^(.+?)\s*=~\s*/(.+)/$}))
51
+ left = match[1].strip
52
+ pattern = match[2].strip
53
+
54
+ left_val = get_value(left, context)
55
+ return false if left_val.nil?
56
+
57
+ !!(left_val.to_s =~ /#{pattern}/)
58
+ # Handle comparisons with ==, !=, >, >=, <, <=
59
+ # Determine if numeric or string comparison based on values
60
+ elsif (match = condition.match(/^(.+?)\s*(==|!=|>=|<=|>|<)\s*(.+)$/))
61
+ left = match[1].strip
62
+ operator = match[2]
63
+ right = match[3].strip
64
+
65
+ left_val = get_value(left, context)
66
+ # If get_value returned nil, try using the original string as a literal
67
+ left_val = left if left_val.nil? && numeric_value(left)
68
+ right_val = get_value(right, context)
69
+ # If get_value returned nil, try using the original string as a literal
70
+ right_val = right if right_val.nil? && numeric_value(right)
71
+
72
+ # Try to convert to numbers
73
+ left_num = numeric_value(left_val)
74
+ right_num = numeric_value(right_val)
75
+
76
+ # If both are numeric, use numeric comparison
77
+ if left_num && right_num
78
+ case operator
79
+ when '=='
80
+ left_num == right_num
81
+ when '!='
82
+ left_num != right_num
83
+ when '>'
84
+ left_num > right_num
85
+ when '>='
86
+ left_num >= right_num
87
+ when '<'
88
+ left_num < right_num
89
+ when '<='
90
+ left_num <= right_num
91
+ else
92
+ false
93
+ end
94
+ # Otherwise use string comparison for == and !=, or return false for others
95
+ else
96
+ case operator
97
+ when '=='
98
+ # Handle nil comparisons
99
+ left_val.nil? == right_val.nil? && (left_val.nil? || left_val.to_s == right_val.to_s)
100
+ when '!='
101
+ left_val.nil? != right_val.nil? || (!left_val.nil? && !right_val.nil? && left_val.to_s != right_val.to_s)
102
+ else
103
+ # For >, >=, <, <=, return false if not numeric
104
+ false
105
+ end
106
+ end
107
+ # Handle string-only comparisons: **= (fuzzy match), *= (contains), ^= (starts with), $= (ends with)
108
+ # Note: **= must come before *= in the regex to avoid matching *= first
109
+ elsif (match = condition.match(/^(.+?)\s*(\*\*=|\*=|\^=|\$=)\s*(.+)$/))
110
+ left = match[1].strip
111
+ operator = match[2]
112
+ right = match[3].strip
113
+
114
+ left_val = get_value(left, context)
115
+ right_val = get_value(right, context)
116
+ # If right side is nil (variable not found), treat it as a literal string
117
+ right_val = right if right_val.nil?
118
+
119
+ return false if left_val.nil? || right_val.nil?
120
+
121
+ case operator
122
+ when '*='
123
+ left_val.to_s.include?(right_val.to_s)
124
+ when '^='
125
+ left_val.to_s.start_with?(right_val.to_s)
126
+ when '$='
127
+ left_val.to_s.end_with?(right_val.to_s)
128
+ when '**='
129
+ # Fuzzy match: split search string into chars and join with .*? for regex
130
+ pattern = "^.*?#{right_val.to_s.split('').map { |c| Regexp.escape(c) }.join('.*?')}.*?$"
131
+ !!(left_val.to_s =~ /#{pattern}/)
132
+ else
133
+ false
134
+ end
135
+ # Simple existence check (just a variable name)
136
+ else
137
+ val = get_value(condition, context)
138
+ !val.nil? && val.to_s != ''
139
+ end
140
+ end
141
+
142
+ ##
143
+ ## Get value from various sources
144
+ ##
145
+ def get_value(expr, context)
146
+ expr = expr.strip
147
+
148
+ # Remove quotes if present
149
+ return Regexp.last_match(1) if expr =~ /^["'](.+)["']$/
150
+
151
+ # Remove ${} wrapper if present (for consistency with variable substitution syntax)
152
+ expr = Regexp.last_match(1) if expr =~ /^\$\{(.+)\}$/
153
+
154
+ # Check positional arguments
155
+ if expr =~ /^\$(\d+)$/
156
+ idx = Regexp.last_match(1).to_i - 1
157
+ return Howzit.arguments[idx] if Howzit.arguments && Howzit.arguments[idx]
158
+ end
159
+
160
+ # Check named arguments
161
+ return Howzit.named_arguments[expr.to_sym] if Howzit.named_arguments&.key?(expr.to_sym)
162
+ return Howzit.named_arguments[expr] if Howzit.named_arguments&.key?(expr)
163
+
164
+ # Check metadata (from context only, to avoid circular dependencies)
165
+ metadata = context[:metadata]
166
+ return metadata[expr] if metadata&.key?(expr)
167
+ return metadata[expr.downcase] if metadata&.key?(expr.downcase)
168
+
169
+ # Check environment variables
170
+ return ENV[expr] if ENV.key?(expr)
171
+ return ENV[expr.upcase] if ENV.key?(expr.upcase)
172
+
173
+ # Check for special values: cwd, working directory
174
+ return Dir.pwd if expr =~ /^(cwd|working\s+directory)$/i
175
+
176
+ # Return nil if nothing matched (variable is undefined)
177
+ nil
178
+ end
179
+
180
+ ##
181
+ ## Convert value to numeric if possible
182
+ ##
183
+ def numeric_value(val)
184
+ return val if val.is_a?(Numeric)
185
+
186
+ str = val.to_s.strip
187
+ return nil if str.empty?
188
+
189
+ # Try integer first
190
+ return str.to_i if str =~ /^-?\d+$/
191
+
192
+ # Try float
193
+ return str.to_f if str =~ /^-?\d+\.\d+$/
194
+
195
+ nil
196
+ end
197
+
198
+ ##
199
+ ## Evaluate special conditions
200
+ ##
201
+ def evaluate_special(condition, context)
202
+ condition = condition.downcase.strip
203
+
204
+ if condition =~ /^git\s+dirty$/i
205
+ git_dirty?
206
+ elsif condition =~ /^git\s+clean$/i
207
+ !git_dirty?
208
+ elsif (match = condition.match(/^file\s+exists\s+(.+)$/i))
209
+ file = match[1].strip
210
+ # get_value returns nil if not found, so use original path if nil
211
+ file_val = get_value(file, context)
212
+ file_path = file_val.nil? ? file : file_val.to_s
213
+ File.exist?(file_path) && !File.directory?(file_path)
214
+ elsif (match = condition.match(/^dir\s+exists\s+(.+)$/i))
215
+ dir = match[1].strip
216
+ dir_val = get_value(dir, context)
217
+ dir_path = dir_val.nil? ? dir : dir_val.to_s
218
+ File.directory?(dir_path)
219
+ elsif (match = condition.match(/^topic\s+exists\s+(.+)$/i))
220
+ topic_name = match[1].strip
221
+ topic_name = get_value(topic_name, context).to_s
222
+ find_topic(topic_name)
223
+ else
224
+ false
225
+ end
226
+ end
227
+
228
+ ##
229
+ ## Check if git repository is dirty
230
+ ##
231
+ def git_dirty?
232
+ return false unless `which git`.strip != ''
233
+
234
+ Dir.chdir(Dir.pwd) do
235
+ `git diff --quiet 2>/dev/null`
236
+ $CHILD_STATUS.exitstatus != 0
237
+ end
238
+ end
239
+
240
+ ##
241
+ ## Check if topic exists in buildnote
242
+ ##
243
+ def find_topic(topic_name)
244
+ return false unless Howzit.buildnote
245
+
246
+ matches = Howzit.buildnote.find_topic(topic_name)
247
+ !matches.empty?
248
+ end
249
+
250
+ ##
251
+ ## Evaluate file contents condition
252
+ ## Reads file and performs string comparison
253
+ ##
254
+ def evaluate_file_contents(condition, context)
255
+ match = condition.match(/^file\s+contents\s+(.+?)\s+(\*\*=|\*=|\^=|\$=|==|!=|=~)\s*(.+)$/i)
256
+ return false unless match
257
+
258
+ file_path = match[1].strip
259
+ operator = match[2]
260
+ search_value = match[3].strip
261
+
262
+ # Resolve file path (could be a variable)
263
+ file_path_val = get_value(file_path, context)
264
+ file_path = file_path_val.nil? ? file_path : file_path_val.to_s
265
+
266
+ # Resolve search value (could be a variable)
267
+ search_val = get_value(search_value, context)
268
+ search_val = search_val.nil? ? search_value : search_val.to_s
269
+
270
+ # Read file contents
271
+ return false unless File.exist?(file_path) && !File.directory?(file_path)
272
+
273
+ begin
274
+ file_contents = File.read(file_path).strip
275
+ rescue StandardError
276
+ return false
277
+ end
278
+
279
+ # Perform comparison based on operator
280
+ case operator
281
+ when '=='
282
+ file_contents == search_val.to_s
283
+ when '!='
284
+ file_contents != search_val.to_s
285
+ when '*='
286
+ file_contents.include?(search_val.to_s)
287
+ when '^='
288
+ file_contents.start_with?(search_val.to_s)
289
+ when '$='
290
+ file_contents.end_with?(search_val.to_s)
291
+ when '**='
292
+ # Fuzzy match: split search string into chars and join with .*? for regex
293
+ pattern = "^.*?#{search_val.to_s.split('').map { |c| Regexp.escape(c) }.join('.*?')}.*?$"
294
+ !!(file_contents =~ /#{pattern}/)
295
+ when '=~'
296
+ # Regex match - search_value should be a regex pattern
297
+ pattern = search_val.to_s
298
+ # Remove leading/trailing slashes if present
299
+ pattern = pattern[1..-2] if pattern.start_with?('/') && pattern.end_with?('/')
300
+ !!(file_contents =~ /#{pattern}/)
301
+ else
302
+ false
303
+ end
304
+ end
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Howzit
4
+ # Conditional Content processor
5
+ # Handles @if/@unless/@end blocks in topic content
6
+ module ConditionalContent
7
+ class << self
8
+ ##
9
+ ## Process conditional blocks in content
10
+ ##
11
+ ## @param content [String] The content to process
12
+ ## @param context [Hash] Context for condition evaluation
13
+ ##
14
+ ## @return [String] Content with conditional blocks processed
15
+ ##
16
+ def process(content, context = {})
17
+ lines = content.split(/\n/)
18
+ output = []
19
+ condition_stack = []
20
+ # Track if any condition in the current chain has been true
21
+ # This is used for @elsif and @else to know if a previous branch matched
22
+ chain_matched_stack = []
23
+
24
+ lines.each do |line|
25
+ # Check for @if or @unless
26
+ if line =~ /^@(if|unless)\s+(.+)$/i
27
+ directive = Regexp.last_match(1).downcase
28
+ condition = Regexp.last_match(2).strip
29
+
30
+ # Evaluate condition
31
+ result = ConditionEvaluator.evaluate(condition, context)
32
+ # For @unless, negate the result
33
+ result = !result if directive == 'unless'
34
+
35
+ condition_stack << result
36
+ chain_matched_stack << result
37
+
38
+ # Don't include the @if/@unless line itself
39
+ next
40
+ end
41
+
42
+ # Check for @elsif
43
+ if line =~ /^@elsif\s+(.+)$/i
44
+ condition = Regexp.last_match(1).strip
45
+
46
+ # If previous condition in chain was true, this branch is false
47
+ # Otherwise, evaluate the condition
48
+ if !condition_stack.empty? && chain_matched_stack.last
49
+ # Previous branch matched, so this one is false
50
+ condition_stack[-1] = false
51
+ else
52
+ # Previous branch didn't match, evaluate this condition
53
+ result = ConditionEvaluator.evaluate(condition, context)
54
+ condition_stack[-1] = result
55
+ chain_matched_stack[-1] = result if result
56
+ end
57
+
58
+ # Don't include the @elsif line itself
59
+ next
60
+ end
61
+
62
+ # Check for @else
63
+ if line =~ /^@else\s*$/i
64
+ # If any previous condition in chain was true, this branch is false
65
+ # Otherwise, this branch is true
66
+ if !condition_stack.empty? && chain_matched_stack.last
67
+ # Previous branch matched, so else is false
68
+ condition_stack[-1] = false
69
+ else
70
+ # No previous branch matched, so else is true
71
+ condition_stack[-1] = true
72
+ chain_matched_stack[-1] = true
73
+ end
74
+
75
+ # Don't include the @else line itself
76
+ next
77
+ end
78
+
79
+ # Check for @end - only skip if it's closing an @if/@unless/@elsif/@else block
80
+ if (line =~ /^@end\s*$/) && !condition_stack.empty?
81
+ # This @end closes a conditional block, so skip it
82
+ condition_stack.pop
83
+ chain_matched_stack.pop
84
+ next
85
+ end
86
+ # Otherwise, this @end is for @before/@after, so include it
87
+
88
+ # Include the line only if all conditions in stack are true
89
+ output << line if condition_stack.all? { |cond| cond }
90
+ end
91
+
92
+ output.join("\n")
93
+ end
94
+ end
95
+ end
96
+ end
data/lib/howzit/config.rb CHANGED
@@ -129,14 +129,26 @@ module Howzit
129
129
 
130
130
  ## Update editor config
131
131
  def update_editor
132
- puts 'No $EDITOR defined, no value in config'
132
+ begin
133
+ puts 'No $EDITOR defined, no value in config'
134
+ rescue Errno::EPIPE
135
+ # Pipe closed, ignore
136
+ end
133
137
  editor = Prompt.read_editor
134
138
  if editor.nil?
135
- puts 'Cancelled, no editor stored.'
139
+ begin
140
+ puts 'Cancelled, no editor stored.'
141
+ rescue Errno::EPIPE
142
+ # Pipe closed, ignore
143
+ end
136
144
  Process.exit 1
137
145
  end
138
146
  update_config_option({ config_editor: editor, editor: editor })
139
- puts "Default editor set to #{editor}, modify in config file"
147
+ begin
148
+ puts "Default editor set to #{editor}, modify in config file"
149
+ rescue Errno::EPIPE
150
+ # Pipe closed, ignore
151
+ end
140
152
  editor
141
153
  end
142
154
 
@@ -39,7 +39,13 @@ module Howzit
39
39
  ## @param level [Symbol] The level
40
40
  ##
41
41
  def write(msg, level = :info)
42
- $stderr.puts msg if LOG_LEVELS[level] >= @log_level
42
+ return unless LOG_LEVELS[level] >= @log_level
43
+
44
+ begin
45
+ $stderr.puts msg
46
+ rescue Errno::EPIPE
47
+ # Pipe closed, ignore
48
+ end
43
49
  end
44
50
 
45
51
  ##
@@ -66,7 +72,13 @@ module Howzit
66
72
  ## @param msg The message
67
73
  ##
68
74
  def warn(msg)
69
- write msg, :warn
75
+ return unless LOG_LEVELS[:warn] >= @log_level
76
+
77
+ begin
78
+ $stderr.puts msg
79
+ rescue Errno::EPIPE
80
+ # Pipe closed, ignore
81
+ end
70
82
  end
71
83
 
72
84
  ##
data/lib/howzit/prompt.rb CHANGED
@@ -100,9 +100,7 @@ module Howzit
100
100
  return fzf_result(res)
101
101
  end
102
102
 
103
- if Util.command_exist?('gum')
104
- return gum_choose(matches, query: query, multi: true)
105
- end
103
+ return gum_choose(matches, query: query, multi: true) if Util.command_exist?('gum')
106
104
 
107
105
  tty_menu(matches, query: query)
108
106
  end
@@ -150,7 +148,11 @@ module Howzit
150
148
  end
151
149
 
152
150
  if query
153
- puts "\nSelect a topic for `#{query}`:"
151
+ begin
152
+ puts "\nSelect a topic for `#{query}`:"
153
+ rescue Errno::EPIPE
154
+ # Pipe closed, ignore
155
+ end
154
156
  end
155
157
  options_list(matches)
156
158
  read_selection(matches)
@@ -168,7 +170,11 @@ module Howzit
168
170
 
169
171
  return [matches[line - 1]] if line.positive? && line <= matches.length
170
172
 
171
- puts 'Out of range'
173
+ begin
174
+ puts 'Out of range'
175
+ rescue Errno::EPIPE
176
+ # Pipe closed, ignore
177
+ end
172
178
  read_selection(matches)
173
179
  end
174
180
  ensure
@@ -229,9 +235,7 @@ module Howzit
229
235
  return res.empty? ? [] : res.split(/\n/)
230
236
  end
231
237
 
232
- if Util.command_exist?('gum')
233
- return gum_choose(matches, prompt: prompt_text, multi: true, required: false)
234
- end
238
+ return gum_choose(matches, prompt: prompt_text, multi: true, required: false) if Util.command_exist?('gum')
235
239
 
236
240
  text_template_input(matches)
237
241
  end
@@ -265,7 +269,11 @@ module Howzit
265
269
  exit
266
270
  end
267
271
 
268
- puts "\n{bw}Available templates:{x} #{available.join(', ')}".c
272
+ begin
273
+ puts "\n{bw}Available templates:{x} #{available.join(', ')}".c
274
+ rescue Errno::EPIPE
275
+ # Pipe closed, ignore
276
+ end
269
277
  printf '{bw}Enter templates to include, comma-separated (return to skip):{x} '.c
270
278
  input = Readline.readline('', true).strip
271
279
 
@@ -321,7 +329,7 @@ module Howzit
321
329
  ## @return [String] the entered value
322
330
  ##
323
331
  def get_line(prompt_text, default: nil)
324
- return (default || '') unless $stdout.isatty
332
+ return default || '' unless $stdout.isatty
325
333
 
326
334
  if Util.command_exist?('gum')
327
335
  result = gum_input(prompt_text, placeholder: default || '')
@@ -346,7 +354,7 @@ module Howzit
346
354
  ##
347
355
  def gum_choose(matches, prompt: nil, multi: false, required: true, query: nil)
348
356
  prompt_text = prompt || (query ? "Select for '#{query}'" : 'Select')
349
- args = ['gum', 'choose']
357
+ args = %w[gum choose]
350
358
  args << '--no-limit' if multi
351
359
  args << "--header=#{Shellwords.escape(prompt_text)}"
352
360
  args << '--cursor.foreground=6'
@@ -376,7 +384,7 @@ module Howzit
376
384
  ## @return [String] The entered value
377
385
  ##
378
386
  def gum_input(prompt_text, placeholder: '')
379
- args = ['gum', 'input']
387
+ args = %w[gum input]
380
388
  args << "--header=#{Shellwords.escape(prompt_text)}"
381
389
  args << "--placeholder=#{Shellwords.escape(placeholder)}" unless placeholder.empty?
382
390
  args << '--cursor.foreground=6'
@@ -57,7 +57,7 @@ module Howzit
57
57
 
58
58
  # Build the table with emoji header - center emoji in 6-char column
59
59
  header = "| 🚥 | #{'Task'.ljust(task_width)} |"
60
- separator = "| :--: | #{':' + '-' * (task_width - 1)} |"
60
+ separator = "| :--: | #{":#{'-' * (task_width - 1)}"} |"
61
61
 
62
62
  table_lines = [header, separator]
63
63
  rows.each do |row|
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Howzit
4
+ # Script Communication module
5
+ # Handles communication from scripts to Howzit via a communication file
6
+ module ScriptComm
7
+ class << self
8
+ ##
9
+ ## Create a communication file for scripts to write to
10
+ ##
11
+ ## @return [String] Path to the communication file
12
+ ##
13
+ def create_comm_file
14
+ file = Tempfile.new('howzit_comm')
15
+ file.close
16
+ file.path
17
+ end
18
+
19
+ ##
20
+ ## Set up the communication file and environment variable
21
+ ##
22
+ ## @return [String] Path to the communication file
23
+ ##
24
+ def setup
25
+ comm_file = create_comm_file
26
+ ENV['HOWZIT_COMM_FILE'] = comm_file
27
+ comm_file
28
+ end
29
+
30
+ ##
31
+ ## Read and process the communication file after script execution
32
+ ##
33
+ ## @param comm_file [String] Path to the communication file
34
+ ##
35
+ ## @return [Hash] Hash with :logs and :vars keys
36
+ ##
37
+ def process(comm_file)
38
+ return { logs: [], vars: {} } unless File.exist?(comm_file)
39
+
40
+ logs = []
41
+ vars = {}
42
+
43
+ begin
44
+ content = File.read(comm_file)
45
+ content.each_line do |line|
46
+ line = line.strip
47
+ next if line.empty?
48
+
49
+ case line
50
+ when /^LOG:(info|warn|error|debug):(.+)$/i
51
+ level = Regexp.last_match(1).downcase.to_sym
52
+ message = Regexp.last_match(2)
53
+ logs << { level: level, message: message }
54
+ when /^VAR:([A-Z0-9_]+)=(.*)$/i
55
+ key = Regexp.last_match(1)
56
+ value = Regexp.last_match(2)
57
+ vars[key] = value
58
+ end
59
+ end
60
+ rescue StandardError => e
61
+ Howzit.console&.warn("Error reading communication file: #{e.message}")
62
+ ensure
63
+ # Clean up the file
64
+ File.unlink(comm_file) if File.exist?(comm_file)
65
+ end
66
+
67
+ { logs: logs, vars: vars }
68
+ end
69
+
70
+ ##
71
+ ## Process communication and apply logs/variables
72
+ ##
73
+ ## @param comm_file [String] Path to the communication file
74
+ ##
75
+ def apply(comm_file)
76
+ result = process(comm_file)
77
+ return if result[:logs].empty? && result[:vars].empty?
78
+
79
+ # Apply log messages
80
+ result[:logs].each do |log_entry|
81
+ level = log_entry[:level]
82
+ message = log_entry[:message]
83
+ next unless Howzit.console
84
+
85
+ case level
86
+ when :info
87
+ Howzit.console.info(message)
88
+ when :warn
89
+ Howzit.console.warn(message)
90
+ when :error
91
+ Howzit.console.error(message)
92
+ when :debug
93
+ Howzit.console.debug(message)
94
+ end
95
+ end
96
+
97
+ # Apply variables to named_arguments
98
+ return if result[:vars].empty?
99
+
100
+ Howzit.named_arguments ||= {}
101
+ Howzit.named_arguments.merge!(result[:vars])
102
+ end
103
+ end
104
+ end
105
+ end
@@ -38,7 +38,7 @@ module Howzit
38
38
  position = 0
39
39
  in_order = 0
40
40
  chars.each do |char|
41
- new_pos = self[position..-1] =~ /#{char}/i
41
+ new_pos = self[position..] =~ /#{char}/i
42
42
  if new_pos
43
43
  position += new_pos
44
44
  in_order += 1
@@ -72,7 +72,7 @@ module Howzit
72
72
  ##
73
73
  def distance(chars)
74
74
  distance = 0
75
- max = self.length - chars.length
75
+ max = length - chars.length
76
76
  return max unless in_order(chars) == chars.length
77
77
 
78
78
  while distance < max
@@ -219,7 +219,7 @@ module Howzit
219
219
  def uncolor
220
220
  # force UTF-8 and remove invalid characters, then remove color codes
221
221
  # and iTerm markers
222
- gsub(Howzit::Color::COLORED_REGEXP, "").gsub(/\e\]1337;SetMark/, "")
222
+ gsub(Howzit::Color::COLORED_REGEXP, '').gsub(/\e\]1337;SetMark/, '')
223
223
  end
224
224
 
225
225
  # Wrap text at a specified width.
@@ -371,7 +371,7 @@ module Howzit
371
371
  gsub!(/\$\{(?<name>[A-Z0-9_]+(?::.*?)?)\}/i) do
372
372
  m = Regexp.last_match
373
373
  arg, default = m['name'].split(/:/).map(&:strip)
374
- if Howzit.named_arguments && Howzit.named_arguments.key?(arg) && !Howzit.named_arguments[arg].nil?
374
+ if Howzit.named_arguments&.key?(arg) && !Howzit.named_arguments[arg].nil?
375
375
  Howzit.named_arguments[arg]
376
376
  elsif default
377
377
  default