Module: Howzit::ScriptSupport

Defined in:
lib/howzit/script_support.rb

Overview

Script Support module Handles helper script installation and injection for run blocks rubocop:disable Metrics/ModuleLength

Constant Summary collapse

LEGACY_SUPPORT_DIR =

Prefer XDG_CONFIG_HOME/howzit/support if set, otherwise ~/.config/howzit/support. For backwards compatibility, we detect an existing ~/.local/share/howzit directory and can optionally migrate it to the new location.

File.join(Dir.home, '.local', 'share', 'howzit')
SUPPORT_DIR =
if ENV['XDG_CONFIG_HOME'] && !ENV['XDG_CONFIG_HOME'].empty?
  File.join(ENV['XDG_CONFIG_HOME'], 'howzit', 'support')
else
  File.join(Dir.home, '.config', 'howzit', 'support')
end

Class Method Summary collapse

Class Method Details

.detect_interpreter(script_content) ⇒ Symbol?

Detect interpreter from hashbang line

Parameters:

  • script_content (String)

    The script content

Returns:

  • (Symbol, nil)

    Language identifier (:bash, :zsh, :fish, :ruby, :python, etc.)



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/howzit/script_support.rb', line 132

def detect_interpreter(script_content)
  first_line = script_content.lines.first&.strip
  return nil unless first_line&.start_with?('#!')

  shebang = first_line.sub(/^#!/, '').strip

  case shebang
  when %r{/bin/bash}, %r{/usr/bin/env bash}
    :bash
  when %r{/bin/zsh}, %r{/usr/bin/env zsh}
    :zsh
  when %r{/bin/fish}, %r{/usr/bin/env fish}
    :fish
  when %r{/usr/bin/env ruby}, %r{/usr/bin/ruby}, %r{/usr/local/bin/ruby}
    :ruby
  when %r{/usr/bin/env python3?}, %r{/usr/bin/python3?}, %r{/usr/local/bin/python3?}
    :python
  when %r{/usr/bin/env perl}, %r{/usr/bin/perl}
    :perl
  when %r{/usr/bin/env node}, %r{/usr/bin/node}
    :node
  end
end

.ensure_support_dirObject

Ensure support directory exists and is populated



34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/howzit/script_support.rb', line 34

def ensure_support_dir
  dir = support_dir
  legacy_dir = File.expand_path(LEGACY_SUPPORT_DIR)
  File.expand_path(File.join(SUPPORT_DIR, '..'))

  # If legacy files exist, always offer to migrate them before proceeding.
  # Use early_init=false here since config is already loaded by the time we reach this point
  migrate_legacy_support(early_init: false) if File.directory?(legacy_dir)

  FileUtils.mkdir_p(dir) unless File.directory?(dir)
  install_helper_scripts
  dir
end

.execution_command_for(script_path, interpreter) ⇒ String

Get the command to execute a script based on interpreter

Parameters:

  • script_path (String)

    Path to the script file

  • interpreter (Symbol, nil)

    The interpreter type

Returns:

  • (String)

    Command to execute the script



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/howzit/script_support.rb', line 219

def execution_command_for(script_path, interpreter)
  cmd = case interpreter
        when :bash
          "/bin/bash #{Shellwords.escape(script_path)}"
        when :zsh
          "/bin/zsh #{Shellwords.escape(script_path)}"
        when :fish
          "/usr/bin/env fish #{Shellwords.escape(script_path)}"
        when :ruby
          "/usr/bin/env ruby #{Shellwords.escape(script_path)}"
        when :python
          "/usr/bin/env python3 #{Shellwords.escape(script_path)}"
        when :perl
          "/usr/bin/env perl #{Shellwords.escape(script_path)}"
        when :node
          "/usr/bin/env node #{Shellwords.escape(script_path)}"
        end
  # Fallback to direct execution if interpreter not recognized
  cmd || script_path
end

.inject_helper(script_content) ⇒ Array

Inject helper script loading into script content

Parameters:

  • script_content (String)

    The original script content

Returns:

  • (Array)
    modified_content, interpreter

    Script content with injection added and interpreter



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/howzit/script_support.rb', line 188

def inject_helper(script_content)
  interpreter = detect_interpreter(script_content)
  return [script_content, nil] unless interpreter

  injection = injection_line_for(interpreter)
  return [script_content, interpreter] unless injection

  lines = script_content.lines
  injection_lines = injection.split("\n").map { |l| "#{l}\n" }
  # Find the hashbang line
  if lines.first&.strip&.start_with?('#!')
    # Insert after hashbang
    injection_lines.each_with_index do |line, idx|
      lines.insert(1 + idx, line)
    end
  else
    # No hashbang, prepend
    lines = injection_lines + lines
  end

  [lines.join, interpreter]
end

.injection_line_for(interpreter) ⇒ String?

Get the injection line for a given interpreter

Parameters:

  • interpreter (Symbol)

    The interpreter type

Returns:

  • (String, nil)

    The injection line to add



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/howzit/script_support.rb', line 163

def injection_line_for(interpreter)
  support_path = support_dir
  case interpreter
  when :bash, :zsh
    "source \"#{support_path}/howzit.sh\""
  when :fish
    "source \"#{support_path}/howzit.fish\""
  when :ruby
    "require '#{support_path}/howzit.rb'"
  when :python
    "import sys\nsys.path.insert(0, '#{support_path}')\nimport howzit"
  when :perl
    "require '#{support_path}/howzit.pl'"
  when :node
    "require('#{support_path}/howzit.js')"
  end
end

.install_helper_scriptsObject

Install all helper scripts



243
244
245
246
247
248
249
250
251
252
253
# File 'lib/howzit/script_support.rb', line 243

def install_helper_scripts
  dir = support_dir
  FileUtils.mkdir_p(dir)

  install_bash_helper(dir)
  install_fish_helper(dir)
  install_ruby_helper(dir)
  install_python_helper(dir)
  install_perl_helper(dir)
  install_node_helper(dir)
end

.migrate_legacy_support(early_init: false) ⇒ Object

Migrate legacy ~/.local/share/howzit directory into the new config root, merging contents and removing the legacy directory when complete.

Parameters:

  • early_init (Boolean) (defaults to: false)

    If true, use simple prompt that doesn’t access Howzit.options



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/howzit/script_support.rb', line 72

def migrate_legacy_support(early_init: false)
  legacy_dir = File.expand_path(LEGACY_SUPPORT_DIR)
  new_root = File.expand_path(File.join(SUPPORT_DIR, '..'))

  unless File.directory?(legacy_dir)
    return if early_init

    Howzit.console.info "No legacy Howzit directory found at #{legacy_dir}; nothing to migrate."
    return

  end

  prompt = "Migrate Howzit files from #{legacy_dir} to #{new_root}? This will overwrite files in the new location with legacy versions, " \
           'add new files, and leave any extra files in the new location in place.'

  if early_init
    unless simple_yn_prompt(prompt, default: true)
      warn 'Migration cancelled; no changes made.'
      return
    end
  else
    unless Prompt.yn(prompt, default: true)
      Howzit.console.info 'Migration cancelled; no changes made.'
      return
    end
  end

  FileUtils.mkdir_p(new_root) unless File.directory?(new_root)

  # Merge legacy into new_root:
  # - overwrite files in new_root with versions from legacy
  # - add files that do not yet exist
  # - leave files that exist only in new_root untouched
  Dir.children(legacy_dir).each do |entry|
    src = File.join(legacy_dir, entry)
    dest = File.join(new_root, entry)

    if File.directory?(src)
      FileUtils.mkdir_p(dest)
      FileUtils.cp_r(File.join(src, '.'), dest)
    else
      FileUtils.cp(src, dest)
    end
  end

  FileUtils.rm_rf(legacy_dir)
  if early_init
    warn "Migrated Howzit files from #{legacy_dir} to #{new_root}."
  else
    Howzit.console.info "Migrated Howzit files from #{legacy_dir} to #{new_root}."
  end
end

.simple_yn_prompt(prompt, default: true) ⇒ Object

Simple Y/N prompt that doesn’t depend on Howzit.options (for use during early config initialization)



51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/howzit/script_support.rb', line 51

def simple_yn_prompt(prompt, default: true)
  return default unless $stdout.isatty

  tty_state = `stty -g`
  yn = default ? '[Y/n]' : '[y/N]'
  $stdout.syswrite "\e[1;37m#{prompt} #{yn}\e[1;37m? \e[0m"
  system 'stty raw -echo cbreak isig'
  res = $stdin.sysread 1
  res.chomp!
  puts
  system 'stty cooked'
  system "stty #{tty_state}"
  res.empty? ? default : res =~ /y/i
end

.support_dirString

Get the support directory path

Returns:

  • (String)

    expanded path to support directory



27
28
29
# File 'lib/howzit/script_support.rb', line 27

def support_dir
  File.expand_path(SUPPORT_DIR)
end