Module: WifiWand::DocsTooling
- Defined in:
- lib/wifi_wand/docs_tooling.rb
Constant Summary collapse
- REPO_ROOT =
File.('../..', __dir__)
- WORKSPACE_PREPARATION_ERROR =
StandardError excludes process-control and VM-level exceptions like Interrupt, SystemExit, and NoMemoryError.
StandardError
Class Method Summary collapse
- .build_script_path ⇒ Object
- .cleanup_mkdocs_workspace! ⇒ Object
- .clone_relative_target(absolute_target) ⇒ Object
- .copy_docs_source(relative_path) ⇒ Object
- .copy_docs_tree(relative_path, exclude: [], required: true) ⇒ Object
- .directory_index_path(directory) ⇒ Object
- .ensure_mkdocs_available! ⇒ Object
- .ensure_python_available! ⇒ Object
- .excluded_generated_link?(target, markdown_path) ⇒ Boolean
- .excluded_generated_path?(repo_relative_target) ⇒ Boolean
- .executable?(command) ⇒ Boolean
- .extract_rake_passthrough_args!(argv = ARGV, rake_application = Rake.application) ⇒ Object
- .gemfile_path ⇒ Object
- .generated_config_path ⇒ Object
- .generated_docs_dir ⇒ Object
- .generated_exclude_docs(exclude_docs) ⇒ Object
- .generated_nav(nav) ⇒ Object
- .generated_relative_link(absolute_target, markdown_path, fragment: '') ⇒ Object
- .generated_relative_target(target_path, markdown_path) ⇒ Object
- .generated_site_dir ⇒ Object
- .mkdocs_command ⇒ Object
- .mkdocs_config_path ⇒ Object
- .plausible_repo_relative_target?(candidate) ⇒ Boolean
- .prepare_mkdocs_workspace! ⇒ Object
- .python_command ⇒ Object
- .rake_passthrough_args ⇒ Object
- .rakefile_path ⇒ Object
- .repo_relative_link_target(absolute_target) ⇒ Object
- .repository_relative_candidate?(candidate) ⇒ Boolean
- .repository_root_entries ⇒ Object
- .required_docs_tree_exists?(source, relative_path, required) ⇒ Boolean
- .requirements_path ⇒ Object
- .rewrite_excluded_generated_links(markdown, markdown_path) ⇒ Object
- .rewrite_generated_markdown_links ⇒ Object
- .run_mkdocs! ⇒ Object
- .setup_environment! ⇒ Object
- .setup_guidance ⇒ Object
- .setup_script_path ⇒ Object
- .start_server_script_path ⇒ Object
- .venv_dir ⇒ Object
- .venv_mkdocs_path ⇒ Object
- .venv_pip_path ⇒ Object
- .write_generated_config ⇒ Object
Class Method Details
.build_script_path ⇒ Object
27 28 29 |
# File 'lib/wifi_wand/docs_tooling.rb', line 27 def self.build_script_path File.join(REPO_ROOT, 'bin', 'build-docs') end |
.cleanup_mkdocs_workspace! ⇒ Object
130 131 132 133 134 |
# File 'lib/wifi_wand/docs_tooling.rb', line 130 def self.cleanup_mkdocs_workspace! FileUtils.rm_rf(generated_docs_dir) FileUtils.rm_rf(generated_site_dir) FileUtils.rm_f(generated_config_path) end |
.clone_relative_target(absolute_target) ⇒ Object
233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/wifi_wand/docs_tooling.rb', line 233 def self.clone_relative_target(absolute_target) path_parts = absolute_target.split('/').reject(&:empty?) path_parts.each_index do |index| candidate = path_parts[index..].join('/') next unless repository_relative_candidate?(candidate) next unless plausible_repo_relative_target?(candidate) return candidate end nil end |
.copy_docs_source(relative_path) ⇒ Object
143 144 145 146 147 148 149 150 |
# File 'lib/wifi_wand/docs_tooling.rb', line 143 def self.copy_docs_source(relative_path) source = File.join(REPO_ROOT, relative_path) return unless File.exist?(source) destination = File.join(generated_docs_dir, relative_path) FileUtils.mkdir_p(File.dirname(destination)) FileUtils.cp(source, destination) end |
.copy_docs_tree(relative_path, exclude: [], required: true) ⇒ Object
152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/wifi_wand/docs_tooling.rb', line 152 def self.copy_docs_tree(relative_path, exclude: [], required: true) source = File.join(REPO_ROOT, relative_path) return unless required_docs_tree_exists?(source, relative_path, required) destination = File.join(generated_docs_dir, relative_path) FileUtils.mkdir_p(File.dirname(destination)) FileUtils.cp_r(source, File.dirname(destination)) exclude.each do |excluded_file| FileUtils.rm_f(File.join(destination, excluded_file)) end end |
.directory_index_path(directory) ⇒ Object
264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/wifi_wand/docs_tooling.rb', line 264 def self.directory_index_path(directory) index_path = File.join(directory, 'index.md') return index_path if File.exist?(index_path) entries = Dir .children(directory) .reject { |entry| entry.start_with?('.') } .sort body = ["# #{File.basename(directory)}", '', *entries.map { |entry| "- `#{entry}`" }, ''].join("\n") File.write(index_path, body) index_path end |
.ensure_mkdocs_available! ⇒ Object
325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/wifi_wand/docs_tooling.rb', line 325 def self.ensure_mkdocs_available! return if executable?(mkdocs_command) source_command, rake_command = setup_guidance warn 'Error: mkdocs not found. Set up the documentation environment first:' warn " #{source_command}" warn 'or:' warn " #{rake_command}" exit 1 end |
.ensure_python_available! ⇒ Object
337 338 339 340 341 342 343 |
# File 'lib/wifi_wand/docs_tooling.rb', line 337 def self.ensure_python_available! return if executable?(python_command) warn "Error: Python executable \"#{python_command}\" not found." warn 'Install python3 or set WIFIWAND_DOCS_PYTHON to the Python executable path.' exit 1 end |
.excluded_generated_link?(target, markdown_path) ⇒ Boolean
285 286 287 288 289 290 291 |
# File 'lib/wifi_wand/docs_tooling.rb', line 285 def self.excluded_generated_link?(target, markdown_path) target_path = target.split('#', 2).first return false if target_path.empty? || target_path.match?(%r{\A[a-z][a-z0-9+.-]*:}i) excluded_generated_path?(repo_relative_link_target(target_path)) || excluded_generated_path?(generated_relative_target(target_path, markdown_path)) end |
.excluded_generated_path?(repo_relative_target) ⇒ Boolean
301 302 303 304 305 306 307 |
# File 'lib/wifi_wand/docs_tooling.rb', line 301 def self.excluded_generated_path?(repo_relative_target) return false unless repo_relative_target %w[dev/reports dev/prompts].any? do |prefix| repo_relative_target == prefix || repo_relative_target.start_with?("#{prefix}/") end end |
.executable?(command) ⇒ Boolean
71 72 73 74 75 76 77 78 79 80 |
# File 'lib/wifi_wand/docs_tooling.rb', line 71 def self.executable?(command) if command.include?(File::SEPARATOR) File.file?(command) && File.executable?(command) else ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir| path = File.join(dir, command) File.file?(path) && File.executable?(path) end end end |
.extract_rake_passthrough_args!(argv = ARGV, rake_application = Rake.application) ⇒ Object
95 96 97 98 99 100 101 102 103 104 |
# File 'lib/wifi_wand/docs_tooling.rb', line 95 def self.extract_rake_passthrough_args!(argv = ARGV, rake_application = Rake.application) separator_index = argv.index('--') return [] unless separator_index args = argv[(separator_index + 1)..] argv.slice!(separator_index..) task_like_args = args.reject { |arg| arg.start_with?('-') } rake_application.top_level_tasks.reject! { |task| task_like_args.include?(task) } args end |
.gemfile_path ⇒ Object
43 44 45 |
# File 'lib/wifi_wand/docs_tooling.rb', line 43 def self.gemfile_path File.join(REPO_ROOT, 'Gemfile') end |
.generated_config_path ⇒ Object
59 60 61 |
# File 'lib/wifi_wand/docs_tooling.rb', line 59 def self.generated_config_path File.join(REPO_ROOT, 'tmp', "mkdocs-#{Process.pid}.yml") end |
.generated_docs_dir ⇒ Object
55 56 57 |
# File 'lib/wifi_wand/docs_tooling.rb', line 55 def self.generated_docs_dir File.join(REPO_ROOT, 'tmp', "mkdocs-src-#{Process.pid}") end |
.generated_exclude_docs(exclude_docs) ⇒ Object
309 310 311 312 313 314 315 |
# File 'lib/wifi_wand/docs_tooling.rb', line 309 def self.generated_exclude_docs(exclude_docs) exclude_docs .to_s .lines .reject { |line| %w[lib/ spec/].include?(line.strip) } .join end |
.generated_nav(nav) ⇒ Object
183 184 185 186 187 188 189 190 191 192 |
# File 'lib/wifi_wand/docs_tooling.rb', line 183 def self.generated_nav(nav) nav.map do |entry| case entry when Hash entry.transform_values { |value| value == 'index.md' ? 'README.md' : value } else entry end end end |
.generated_relative_link(absolute_target, markdown_path, fragment: '') ⇒ Object
209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/wifi_wand/docs_tooling.rb', line 209 def self.generated_relative_link(absolute_target, markdown_path, fragment: '') repo_relative_target = repo_relative_link_target(absolute_target) return "(#{absolute_target}#{fragment})" unless repo_relative_target generated_target = File.join(generated_docs_dir, repo_relative_target) return "(#{absolute_target}#{fragment})" unless File.exist?(generated_target) generated_target = directory_index_path(generated_target) if File.directory?(generated_target) relative_target = Pathname .new(generated_target) .relative_path_from(Pathname.new(File.dirname(markdown_path))) .to_s "(#{relative_target}#{fragment})" end |
.generated_relative_target(target_path, markdown_path) ⇒ Object
293 294 295 296 297 298 299 |
# File 'lib/wifi_wand/docs_tooling.rb', line 293 def self.generated_relative_target(target_path, markdown_path) absolute_target = File.(target_path, File.dirname(markdown_path)) Pathname .new(absolute_target) .relative_path_from(Pathname.new(generated_docs_dir)) .to_s end |
.generated_site_dir ⇒ Object
63 64 65 |
# File 'lib/wifi_wand/docs_tooling.rb', line 63 def self.generated_site_dir File.join(REPO_ROOT, 'tmp', "mkdocs-site-#{Process.pid}") end |
.mkdocs_command ⇒ Object
82 83 84 85 86 87 88 89 |
# File 'lib/wifi_wand/docs_tooling.rb', line 82 def self.mkdocs_command mkdocs_override = ENV.fetch('WIFIWAND_DOCS_MKDOCS', '').strip return mkdocs_override unless mkdocs_override.empty? return venv_mkdocs_path if executable?(venv_mkdocs_path) 'mkdocs' end |
.mkdocs_config_path ⇒ Object
51 52 53 |
# File 'lib/wifi_wand/docs_tooling.rb', line 51 def self.mkdocs_config_path File.join(REPO_ROOT, 'mkdocs.yml') end |
.plausible_repo_relative_target?(candidate) ⇒ Boolean
246 247 248 249 250 251 |
# File 'lib/wifi_wand/docs_tooling.rb', line 246 def self.plausible_repo_relative_target?(candidate) first_segment = candidate.split('/', 2).first return false unless repository_root_entries.include?(first_segment) candidate.include?('/') || excluded_generated_path?(candidate) end |
.prepare_mkdocs_workspace! ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/wifi_wand/docs_tooling.rb', line 106 def self.prepare_mkdocs_workspace! FileUtils.rm_rf(generated_docs_dir) FileUtils.mkdir_p(generated_docs_dir) copy_docs_source('README.md') copy_docs_source('RELEASE_NOTES.md') copy_docs_source('LICENSE.txt') copy_docs_source('Gemfile') copy_docs_tree('lib') copy_docs_tree('spec') copy_docs_tree('docs', exclude: ['index.md']) copy_docs_tree('dev/docs') copy_docs_tree('dev/reports', required: false) copy_docs_tree('dev/prompts', required: false) copy_docs_tree('logo', required: false) rewrite_generated_markdown_links write_generated_config generated_config_path rescue WORKSPACE_PREPARATION_ERROR cleanup_mkdocs_workspace! raise end |
.python_command ⇒ Object
67 68 69 |
# File 'lib/wifi_wand/docs_tooling.rb', line 67 def self.python_command ENV.fetch('WIFIWAND_DOCS_PYTHON', 'python3') end |
.rake_passthrough_args ⇒ Object
91 92 93 |
# File 'lib/wifi_wand/docs_tooling.rb', line 91 def self.rake_passthrough_args @rake_passthrough_args ||= extract_rake_passthrough_args! end |
.rakefile_path ⇒ Object
39 40 41 |
# File 'lib/wifi_wand/docs_tooling.rb', line 39 def self.rakefile_path File.join(REPO_ROOT, 'Rakefile') end |
.repo_relative_link_target(absolute_target) ⇒ Object
224 225 226 227 228 229 230 231 |
# File 'lib/wifi_wand/docs_tooling.rb', line 224 def self.repo_relative_link_target(absolute_target) current_repo_prefix = "#{REPO_ROOT}/" if absolute_target.start_with?(current_repo_prefix) return absolute_target.delete_prefix(current_repo_prefix) end clone_relative_target(absolute_target) end |
.repository_relative_candidate?(candidate) ⇒ Boolean
253 254 255 256 257 258 |
# File 'lib/wifi_wand/docs_tooling.rb', line 253 def self.repository_relative_candidate?(candidate) return false unless candidate && !candidate.empty? return true if excluded_generated_path?(candidate) File.exist?(File.join(REPO_ROOT, candidate)) end |
.repository_root_entries ⇒ Object
260 261 262 |
# File 'lib/wifi_wand/docs_tooling.rb', line 260 def self.repository_root_entries @repository_root_entries ||= Dir.children(REPO_ROOT).reject { |entry| entry.start_with?('.') } end |
.required_docs_tree_exists?(source, relative_path, required) ⇒ Boolean
165 166 167 168 169 170 |
# File 'lib/wifi_wand/docs_tooling.rb', line 165 def self.required_docs_tree_exists?(source, relative_path, required) return true if File.directory?(source) return false unless required raise "Required documentation source directory is missing: #{relative_path}" end |
.requirements_path ⇒ Object
47 48 49 |
# File 'lib/wifi_wand/docs_tooling.rb', line 47 def self.requirements_path File.join(REPO_ROOT, 'requirements-lock.txt') end |
.rewrite_excluded_generated_links(markdown, markdown_path) ⇒ Object
277 278 279 280 281 282 283 |
# File 'lib/wifi_wand/docs_tooling.rb', line 277 def self.rewrite_excluded_generated_links(markdown, markdown_path) markdown.gsub(%r{\[([^\]]+)\]\(([^)\s]+)\)}) do label = Regexp.last_match(1) target = Regexp.last_match(2) excluded_generated_link?(target, markdown_path) ? label : Regexp.last_match(0) end end |
.rewrite_generated_markdown_links ⇒ Object
194 195 196 197 198 199 200 201 202 203 204 205 206 207 |
# File 'lib/wifi_wand/docs_tooling.rb', line 194 def self.rewrite_generated_markdown_links Dir.glob(File.join(generated_docs_dir, '**', '*.md')).each do |markdown_path| markdown = File.read(markdown_path) rewritten = markdown.gsub(%r{\((/[^)\s]+?)(?::\d+)?(#[^)\s]+)?\)}) do generated_relative_link( Regexp.last_match(1), markdown_path, fragment: Regexp.last_match(2).to_s ) end rewritten = rewrite_excluded_generated_links(rewritten, markdown_path) File.write(markdown_path, rewritten) if rewritten != markdown end end |
.run_mkdocs! ⇒ Object
136 137 138 139 140 141 |
# File 'lib/wifi_wand/docs_tooling.rb', line 136 def self.run_mkdocs!(*) success = system(mkdocs_command, *) exit($CHILD_STATUS&.exitstatus || 1) unless success ensure cleanup_mkdocs_workspace! end |
.setup_environment! ⇒ Object
345 346 347 348 349 |
# File 'lib/wifi_wand/docs_tooling.rb', line 345 def self.setup_environment! ensure_python_available! system(python_command, '-m', 'venv', venv_dir, exception: true) system(venv_pip_path, 'install', '-q', '-r', requirements_path, exception: true) end |
.setup_guidance ⇒ Object
317 318 319 320 321 322 323 |
# File 'lib/wifi_wand/docs_tooling.rb', line 317 def self.setup_guidance source_command = "source #{setup_script_path.shellescape}" rake_command = "BUNDLE_GEMFILE=#{gemfile_path.shellescape} bundle exec rake -f " \ "#{rakefile_path.shellescape} docs:setup" [source_command, rake_command] end |
.setup_script_path ⇒ Object
35 36 37 |
# File 'lib/wifi_wand/docs_tooling.rb', line 35 def self.setup_script_path File.join(REPO_ROOT, 'bin', 'set-up-python-for-doc-server') end |
.start_server_script_path ⇒ Object
31 32 33 |
# File 'lib/wifi_wand/docs_tooling.rb', line 31 def self.start_server_script_path File.join(REPO_ROOT, 'bin', 'start-doc-server') end |
.venv_dir ⇒ Object
15 16 17 |
# File 'lib/wifi_wand/docs_tooling.rb', line 15 def self.venv_dir ENV.fetch('WIFIWAND_DOCS_VENV_DIR', File.join(REPO_ROOT, '.docs-venv')) end |
.venv_mkdocs_path ⇒ Object
19 20 21 |
# File 'lib/wifi_wand/docs_tooling.rb', line 19 def self.venv_mkdocs_path File.join(venv_dir, 'bin', 'mkdocs') end |
.venv_pip_path ⇒ Object
23 24 25 |
# File 'lib/wifi_wand/docs_tooling.rb', line 23 def self.venv_pip_path File.join(venv_dir, 'bin', 'pip') end |
.write_generated_config ⇒ Object
172 173 174 175 176 177 178 179 180 181 |
# File 'lib/wifi_wand/docs_tooling.rb', line 172 def self.write_generated_config config = YAML.load_file(mkdocs_config_path) config['docs_dir'] = generated_docs_dir config['site_dir'] = generated_site_dir config['exclude_docs'] = generated_exclude_docs(config['exclude_docs']) config['nav'] = generated_nav(config['nav']) FileUtils.mkdir_p(File.dirname(generated_config_path)) File.write(generated_config_path, config.to_yaml) end |