howzit 2.1.29 → 2.1.30

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: 3c70619dce51a18faa84ba9516cf1e48bb2b3028f88e22397a8690e75ae65fff
4
- data.tar.gz: 5f28a55772ca45c4f7a755b545c7fbf532460fee20533ff0714a304ea87d6799
3
+ metadata.gz: e1b854ebbde3ca3bdeac5fa4a633911993e6a9361c02ccc8c7364b7624edb4a1
4
+ data.tar.gz: 3a7f716c2f331acb4cb767d0ea3eff2b59dfc82b8756f5716b86b6045d6c46f6
5
5
  SHA512:
6
- metadata.gz: 73247bd6dfbc7dc0b777f88825673dc1fa36575582d29b57c8d3a2a1996d985ad01e72917b8ac3e6626119e9a1182cddad2a166850d9e1cb5bb7aa01e68ffb93
7
- data.tar.gz: 53aa254ba30a35a78880dcb7e9431d354ee4304e8244b290fb6d046dc29c60eb2becb023565ad4072e17c6af69ed5ec6faf062a15a407d87a293d7a67a14ad84
6
+ metadata.gz: f2cec86d81ab9b1b51c65d21b0ef628483e214545bba722267e3670978305d862b634dcdb93db8bad56d66627213a60939784feb6d021d9cbd2bfc9639451bea
7
+ data.tar.gz: b925f6cbe26ac39b5aa18fcd21896885b85742b88098d25e0b1fb35915983da3c602bc912bc44745fe3fd6f92396bcecdf7ff036411ffe872c54145d00fdf888
data/CHANGELOG.md CHANGED
@@ -1,3 +1,77 @@
1
+ ### 2.1.30
2
+
3
+ 2026-01-06 03:55
4
+
5
+ #### CHANGED
6
+
7
+ - Updated rubocop from version 0.93.1 to 1.82.1 for Ruby 3.4.4 compatibility
8
+ - Updated .rubocop.yml to use plugins syntax instead of require for rubocop extensions
9
+ - Updated .rubocop.yml to inherit from .rubocop_todo.yml and removed Max settings that were overriding todo file limits
10
+ - Added Security/YAMLLoad exception to .rubocop.yml to allow YAML.load usage (intentionally not using safe_load)
11
+ - Added Layout/LineLength exceptions for files with intentionally long lines (bin/howzit, task.rb, util.rb, stringutils.rb, buildnote.rb)
12
+ - Run blocks now execute scripts using appropriate interpreter commands instead of always using /bin/sh
13
+ - Moved @log_level and @set_var directive processing before task check in sequential execution to ensure they are processed correctly.
14
+
15
+ #### NEW
16
+
17
+ - Scripts can now communicate back to Howzit by writing to a communication file specified in HOWZIT_COMM_FILE environment variable, allowing scripts to send log messages (LOG:level:message) and set variables (VAR:KEY=value) that are available for subsequent tasks and conditional logic
18
+ - Added ScriptComm module to handle bidirectional communication between scripts and Howzit
19
+ - Added @if and @unless conditional blocks that allow content and tasks to be conditionally included or excluded based on evaluated conditions, with support for nested blocks
20
+ - Conditional blocks support string comparisons (==, =~ /regex/, *= contains, ^= starts with, $= ends with) and numeric comparisons (==, !=, >, >=, <, <=)
21
+ - Conditions can test against metadata keys, environment variables, positional arguments ($1, $2, etc.), named arguments, and script-set variables
22
+ - Added special condition checks: git dirty/clean, file exists <path>, dir exists <path>, topic exists <name>, and cwd/working directory
23
+ - Conditions support negation with 'not' or '!' prefix
24
+ - Added @elsif directive for alternative conditions in @if/@unless blocks, allowing multiple conditional branches
25
+ - Added @else directive for fallback branches in conditional blocks when all previous conditions are false
26
+ - Conditional blocks now support chaining multiple @elsif statements between @if/@unless and @else
27
+ - @elsif and @else work correctly with nested conditional blocks
28
+ - Added **= fuzzy match operator for string comparisons that matches if search string characters appear in order within the target string (e.g., "fluffy" **= "ffy" matches)
29
+ - Added file contents condition that reads file contents and performs string comparisons using any comparison operator (e.g., file contents VERSION.txt ^= 0.)
30
+ - File contents condition supports file paths as variables from metadata, named arguments, or environment variables
31
+ - ScriptSupport module provides helper functions (log_info, log_warn, log_error, log_debug, set_var) for bash, zsh, fish, ruby, python, perl, and node scripts in run blocks
32
+ - Automatic interpreter detection from hashbang lines in scripts
33
+ - Helper script injection into run blocks based on detected interpreter
34
+ - Support directory installation at ~/.local/share/howzit/support with language-specific helper scripts
35
+ - Add sequential conditional evaluation: @if/@unless blocks are now evaluated after each task runs, allowing variables set by scripts to affect subsequent conditional blocks in the same topic
36
+ - Add @log_level(LEVEL) directive to set log level for subsequent tasks in a topic
37
+ - Add log_level parameter to @run directives (e.g., @run(script.sh, log_level=debug))
38
+ - Add HOWZIT_LOG_LEVEL environment variable support for global log level configuration
39
+ - Add emoji and color indicators for log messages (debug, info, warn, error)
40
+ - Add comprehensive test coverage for sequential conditional evaluation including @if/@unless/@elsif/@else blocks with variables from run blocks
41
+ - Add comprehensive test coverage for log level configuration including @log_level directive, log_level parameter in @run directives, and HOWZIT_LOG_LEVEL environment variable
42
+ - Added @set_var directive to set variables in build notes. Takes two comma-separated arguments: variable name (alphanumeric, dashes, underscores only) and value. Variables are available as ${VAR} in subsequent @run directives, run blocks, and @if/@else conditional blocks.
43
+ - Added command substitution support to @set_var directive. Values can use backticks (`command`) or $() syntax ($(command)) to execute commands and use their output as the variable value. Commands can reference other variables using ${VAR} syntax.
44
+ - Added @set_var directive to set variables directly in build notes, making them available as ${VAR} in subsequent @run directives, run blocks, and @if/@else conditional blocks.
45
+ - Added command substitution support to @set_var so values can come from backtick commands (`command`) or $() syntax ($(command)), with command output (whitespace stripped) used as the variable value and ${VAR} substitutions applied inside the command.
46
+
47
+ #### IMPROVED
48
+
49
+ - Auto-corrected rubocop style offenses including string literals, redundant self, parentheses, and other correctable issues
50
+ - Fixed Lint/Void issue in buildnote.rb by simplifying conditional logic
51
+ - Cwd and working directory can now be used with string comparison operators (==, =~, *=, ^=, $=) to check the current directory path
52
+ - Conditions now support ${var} syntax in addition to var for consistency with variable substitution syntax
53
+ - String comparison operators (*=, ^=, $=) now treat unquoted strings that aren't found as variables as literal strings, allowing simpler syntax like template *= gem instead of template *= "gem"
54
+ - Log messages from scripts now display with visual indicators: debug messages show with magnifying glass emoji and dark color, info with info emoji and cyan, warnings with warning emoji and yellow, errors with X emoji and red
55
+ - Log level filtering now properly applies to script-to-howzit communication messages, showing only messages at or above the configured level
56
+ - Conditional blocks (@if/@unless/@elsif/@else) now re-evaluate after each task execution, enabling dynamic conditional flow based on variables set by preceding tasks
57
+ - Improve task directive parsing by refactoring to use unless/next pattern for better code organization and fixing @log_level directive handling
58
+ - Improve Directive#to_task to properly handle title rendering with variable substitution, argument parsing for include tasks, and action escaping for copy tasks
59
+ - Processed @set_var directives before task creation in topics without conditionals so variable substitution in @run actions works as expected even in the non-sequential execution path.
60
+
61
+ #### FIXED
62
+
63
+ - Resolved NameError for 'white' color method by generating escape codes directly from configured_colors hash instead of calling dynamically generated methods
64
+ - Fixed infinite recursion in ConsoleLogger by using $stderr.puts directly instead of calling warn method recursively
65
+ - Color template method now properly respects coloring? setting and returns empty strings when coloring is disabled
66
+ - Resolved test failures caused by Howzit.buildnote caching stale instances by resetting @buildnote in spec_helper before each test
67
+ - Fixed bug where @end statements failed to close conditional blocks when conditions evaluated to false, preventing subsequent conditional blocks from working correctly
68
+ - Fixed issue where named arguments from topic titles were not available when evaluating conditions in conditional blocks
69
+ - Suppressed EPIPE errors that occur when writing to stdout/stderr after pipes are closed, preventing error messages from appearing in terminal output
70
+ - Fix @elsif and @else conditional blocks not executing tasks when parent @if condition is false by correctly tracking branch indices and skipping parent @if index in conditional path evaluation
71
+ - Fix clipboard copy test failing due to cached console logger instance not updating when log_level option changes
72
+ - Fixed variable persistence issue in sequential execution where Howzit.named_arguments was being reset on each iteration, causing @set_var variables to be lost.
73
+ - Ensured variables set by @set_var and helper scripts persist correctly across sequential conditional evaluation by merging topic named arguments into Howzit.named_arguments instead of overwriting them.
74
+
1
75
  ### 2.1.29
2
76
 
3
77
  2026-01-01 06:55
data/README.md CHANGED
@@ -4,11 +4,11 @@
4
4
  [![Gem](https://img.shields.io/gem/v/howzit.svg)](https://rubygems.org/gems/howzit)
5
5
  [![GitHub license](https://img.shields.io/github/license/ttscoff/howzit.svg)](./LICENSE.txt)
6
6
 
7
+
7
8
  A command-line reference tool for tracking project build systems
8
9
 
9
10
  Howzit is a tool that allows you to keep Markdown-formatted notes about a project's tools and procedures. It functions as an easy lookup for notes about a particular task, as well as a task runner to automatically execute appropriate commands.
10
11
 
11
-
12
12
  ## Features
13
13
 
14
14
  - Match topic titles with any portion of title
@@ -16,6 +16,10 @@ Howzit is a tool that allows you to keep Markdown-formatted notes about a projec
16
16
  - Use `@run()`, `@copy()`, and `@open()` to perform actions within a build notes file
17
17
  - Use `@include()` to import another topic's tasks
18
18
  - Use fenced code blocks to include/run embedded scripts
19
+ - Scripts can communicate back to Howzit, sending log messages and setting variables
20
+ - Conditional blocks (`@if`/`@unless`/`@elsif`/`@else`) for conditionally including content and tasks
21
+ - String comparison operators including fuzzy match (`**=`) for flexible pattern matching
22
+ - File contents conditions to check file contents in conditional blocks
19
23
  - Sets iTerm 2 marks on topic titles for navigation when paging is disabled
20
24
  - Inside of git repositories, howzit will work from subdirectories, assuming build notes are in top level of repo
21
25
  - Templates for easily including repeat tasks
data/Rakefile CHANGED
@@ -46,9 +46,15 @@ task :ver do
46
46
  puts "changelog: #{cver}"
47
47
  end
48
48
 
49
+ desc 'Version.rb check'
50
+ task :vver do
51
+ res = `grep VERSION lib/howzit/version.rb`
52
+ print res.match(/VERSION *= *['"](\d+\.\d+\.\d+(\w+)?)/)[1]
53
+ end
54
+
49
55
  desc 'Changelog version check'
50
56
  task :cver do
51
- puts IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
57
+ print IO.read(File.join(File.dirname(__FILE__), 'CHANGELOG.md')).match(/^#+ (\d+\.\d+\.\d+(\w+)?)/)[1]
52
58
  end
53
59
 
54
60
  desc 'Run tests in Docker'
data/lib/howzit/config.rb CHANGED
@@ -194,6 +194,19 @@ module Howzit
194
194
  config = load_config
195
195
  load_theme
196
196
  @options = flags.merge(config)
197
+
198
+ # Check for HOWZIT_LOG_LEVEL environment variable
199
+ return unless ENV['HOWZIT_LOG_LEVEL']
200
+
201
+ level_str = ENV['HOWZIT_LOG_LEVEL'].downcase
202
+ level_map = {
203
+ 'debug' => 0,
204
+ 'info' => 1,
205
+ 'warn' => 2,
206
+ 'warning' => 2,
207
+ 'error' => 3
208
+ }
209
+ @options[:log_level] = level_map[level_str] || level_str.to_i
197
210
  end
198
211
 
199
212
  ##
@@ -31,6 +31,50 @@ module Howzit
31
31
  @log_level = Howzit.options[:log_level]
32
32
  end
33
33
 
34
+ ##
35
+ ## Get emoji for log level
36
+ ##
37
+ ## @param level [Symbol] The level
38
+ ##
39
+ ## @return [String] Emoji for the level
40
+ ##
41
+ def emoji_for_level(level)
42
+ case level
43
+ when :debug
44
+ '🔍'
45
+ when :info
46
+ 'ℹ️'
47
+ when :warn
48
+ '⚠️'
49
+ when :error
50
+ '❌'
51
+ else
52
+ ''
53
+ end
54
+ end
55
+
56
+ ##
57
+ ## Get color prefix for log level
58
+ ##
59
+ ## @param level [Symbol] The level
60
+ ##
61
+ ## @return [String] Color template string
62
+ ##
63
+ def color_for_level(level)
64
+ case level
65
+ when :debug
66
+ '{d}'
67
+ when :info
68
+ '{c}'
69
+ when :warn
70
+ '{y}'
71
+ when :error
72
+ '{r}'
73
+ else
74
+ ''
75
+ end
76
+ end
77
+
34
78
  ##
35
79
  ## Write a message to the console based on the urgency
36
80
  ## level and user's log level setting
@@ -41,8 +85,16 @@ module Howzit
41
85
  def write(msg, level = :info)
42
86
  return unless LOG_LEVELS[level] >= @log_level
43
87
 
88
+ emoji = emoji_for_level(level)
89
+ color = color_for_level(level)
90
+ formatted_msg = if emoji && color
91
+ "#{emoji} #{color}#{msg}{x}".c
92
+ else
93
+ msg
94
+ end
95
+
44
96
  begin
45
- $stderr.puts msg
97
+ $stderr.puts formatted_msg
46
98
  rescue Errno::EPIPE
47
99
  # Pipe closed, ignore
48
100
  end
@@ -74,8 +126,16 @@ module Howzit
74
126
  def warn(msg)
75
127
  return unless LOG_LEVELS[:warn] >= @log_level
76
128
 
129
+ emoji = emoji_for_level(:warn)
130
+ color = color_for_level(:warn)
131
+ formatted_msg = if emoji && color
132
+ "#{emoji} #{color}#{msg}{x}".c
133
+ else
134
+ msg
135
+ end
136
+
77
137
  begin
78
- $stderr.puts msg
138
+ $stderr.puts formatted_msg
79
139
  rescue Errno::EPIPE
80
140
  # Pipe closed, ignore
81
141
  end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module Howzit
6
+ # Directive class
7
+ # Represents a parsed directive from topic content (tasks, conditionals, etc.)
8
+ class Directive
9
+ attr_reader :type, :content, :condition, :directive_type, :optional, :default, :line_number, :conditional_path,
10
+ :log_level_value, :var_name, :var_value
11
+
12
+ ##
13
+ ## Initialize a Directive
14
+ ##
15
+ ## @param type [Symbol] :task, :if, :unless, :elsif, :else, :end, :log_level
16
+ ## @param content [String] The directive content (action, block, etc.)
17
+ ## @param condition [String, nil] Condition string for conditionals
18
+ ## @param directive_type [String, nil] 'if', 'unless', 'elsif', 'else' for conditionals
19
+ ## @param optional [Boolean] Whether task requires confirmation
20
+ ## @param default [Boolean] Default response for confirmation
21
+ ## @param line_number [Integer] Line number in original content
22
+ ## @param conditional_path [Array] Array of conditional indices this directive is nested in
23
+ ## @param log_level_value [String, nil] Log level value for @log_level directives
24
+ ## @param var_name [String, nil] Variable name for @set_var directives
25
+ ## @param var_value [String, nil] Variable value for @set_var directives
26
+ ##
27
+ def initialize(type:, content: nil, condition: nil, directive_type: nil, optional: false, default: true,
28
+ line_number: nil, conditional_path: [], log_level_value: nil, var_name: nil, var_value: nil)
29
+ @type = type
30
+ @content = content
31
+ @condition = condition
32
+ @directive_type = directive_type
33
+ @optional = optional
34
+ @default = default
35
+ @line_number = line_number
36
+ @conditional_path = conditional_path || []
37
+ @log_level_value = log_level_value
38
+ @var_name = var_name
39
+ @var_value = var_value
40
+ end
41
+
42
+ ##
43
+ ## Is this a conditional directive?
44
+ ##
45
+ def conditional?
46
+ %i[if unless elsif else end].include?(@type)
47
+ end
48
+
49
+ ##
50
+ ## Is this a task directive?
51
+ ##
52
+ def task?
53
+ @type == :task
54
+ end
55
+
56
+ ##
57
+ ## Is this a log_level directive?
58
+ ##
59
+ def log_level?
60
+ @type == :log_level
61
+ end
62
+
63
+ ##
64
+ ## Is this a set_var directive?
65
+ ##
66
+ def set_var?
67
+ @type == :set_var
68
+ end
69
+
70
+ ##
71
+ ## Convert directive to a Task object (only works for task directives)
72
+ ##
73
+ ## @param parent [Topic] The parent topic
74
+ ## @param current_log_level [String, nil] Current log level to apply to task
75
+ ##
76
+ ## @return [Task] Task object
77
+ ##
78
+ def to_task(parent, current_log_level: nil)
79
+ return nil unless task?
80
+
81
+ task_data = @content.dup
82
+ task_type = task_data[:type]
83
+
84
+ # Apply current log level if set and task doesn't have its own
85
+ task_data[:log_level] = current_log_level if current_log_level && !task_data[:log_level]
86
+
87
+ # Set named_arguments before processing titles for variable substitution
88
+ Howzit.named_arguments = parent.named_args
89
+
90
+ case task_type
91
+ when :block
92
+ # Block tasks are already properly formatted
93
+ task_data[:parent] = parent
94
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
95
+ when :run
96
+ # Run tasks need title rendering (similar to define_task_args)
97
+ title = task_data[:title]
98
+ title = title.render_arguments if title && !title.empty?
99
+ task_data[:title] = title
100
+ task_data[:parent] = parent
101
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
102
+ when :copy
103
+ # Copy tasks need title rendering and action escaping
104
+ title = task_data[:title]
105
+ title = title.render_arguments if title && !title.empty?
106
+ task_data[:title] = title
107
+ task_data[:action] = Shellwords.escape(task_data[:action])
108
+ task_data[:parent] = parent
109
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
110
+ when :open
111
+ # Open tasks need title rendering
112
+ title = task_data[:title]
113
+ title = title.render_arguments if title && !title.empty?
114
+ task_data[:title] = title
115
+ task_data[:parent] = parent
116
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
117
+ when :include
118
+ # Include tasks need special handling (title processing, arguments, etc.)
119
+ title = task_data[:title]
120
+ if title =~ /\[(.*?)\] *$/
121
+ args = Regexp.last_match(1).split(/ *, */).map(&:render_arguments)
122
+ Howzit.arguments = args
123
+ parent.arguments
124
+ title.sub!(/ *\[.*?\] *$/, '')
125
+ end
126
+ title = title.render_arguments if title && !title.empty?
127
+ task_data[:title] = title
128
+ task_data[:parent] = parent
129
+ task_data[:arguments] = Howzit.named_arguments
130
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
131
+ else
132
+ task_data[:parent] = parent
133
+ Howzit::Task.new(task_data, optional: @optional, default: @default)
134
+ end
135
+ end
136
+ end
137
+ end