module DirectchatMircInventory
DirectchatMircInventory metadata — file purposes, alias notes, var/popup maps. Public: OUTPUT_FILENAME, FILE_PURPOSES, FILE_PHASE, FILE_PORTABLE, TOP_ALIAS_NOTES, VAR_WEB_MAP, VAR_SKIP_PATTERN, POPUP_OS_PATTERN, POPUP_XDCC_PATTERN Depends: (none) Tests: test/lib/directchat_mirc_inventory_test.rb
Constants
- FILE_PHASE
- FILE_PORTABLE
- FILE_PURPOSES
- OUTPUT_FILENAME
- POPUP_OS_PATTERN
- POPUP_XDCC_PATTERN
- TOP_ALIAS_NOTES
- VAR_SKIP_PATTERN
- VAR_WEB_MAP
Public Class Methods
Source
# File lib/directchat_mirc/inventory_report.rb, line 50 def alias_snippet(body, name) return nil unless body body.each_line do |line| stripped = line.strip return stripped.slice(0, 100) if stripped.match?(/\A#{Regexp.escape(name)}\s/i) return stripped.slice(0, 100) if stripped.match?(/\Aalias\s+#{Regexp.escape(name)}\s/i) end "(block body)" end
Source
# File lib/directchat_mirc/inventory_report.rb, line 13 def build_file_rows(files, contents, aliases_by_file, handlers_by_file) files.keys.sort.map do |name| body = contents[name] { name: name, purpose: FILE_PURPOSES[name] || "Legacy script (review manually)", lines: body.lines.length, aliases: aliases_by_file[name]&.length.to_i, handlers: handlers_by_file[name]&.length.to_i, phase: FILE_PHASE[name] || "drop", portable: FILE_PORTABLE[name] || "yes" } end end
Source
# File lib/directchat_mirc/extractors.rb, line 75 def build_popup_row(menu:, label:, command:) classification = classify_popup(command) { menu: menu, label: label, command: command, web: classification[:web], phase: classification[:phase], notes: classification[:notes] } end
Source
# File lib/directchat_mirc/inventory_report.rb, line 28 def build_top_alias_rows(_alias_locations, contents) TOP_ALIAS_NOTES.map do |name, note| file = note[:file] snippet = alias_snippet(contents[file], name) if file note.merge(name: name, snippet: snippet) end end
Source
# File lib/directchat_mirc/extractors.rb, line 87 def classify_popup(command) cmd = command.to_s if cmd.match?(POPUP_OS_PATTERN) return { web: "drop", phase: "drop", notes: "OS-specific" } end if cmd.match?(POPUP_XDCC_PATTERN) return { web: "drop", phase: "drop", notes: "XDCC" } end if cmd.match?(/\bwhois\b/i) return { web: "Context menu → WHOIS partial", phase: "2", notes: nil } end if cmd.match?(/\bctcp\b.*\bping\b/i) return { web: "CTCP ping", phase: "2", notes: nil } end if cmd.match?(/\bctcp\b.*\btime\b/i) return { web: "CTCP time", phase: "2", notes: nil } end if cmd.match?(/\bctcp\b.*\bversion\b/i) return { web: "CTCP version", phase: "2", notes: nil } end if cmd.match?(/\bdcc\s+chat\b/i) return { web: "DCC chat relay", phase: "3", notes: nil } end if cmd.match?(/\bdcc\s+send\b/i) return { web: "File send", phase: "3", notes: nil } end if cmd.match?(/\bignore\b/i) return { web: "Ignore list toggle", phase: "2", notes: nil } end if cmd.match?(/\bnotify\b/i) return { web: "Notify list toggle", phase: "2", notes: nil } end if cmd.match?(/\btopic\b/i) return { web: "Topic dialog", phase: "2", notes: nil } end if cmd.match?(/\bmode\b/i) || cmd.match?(/[+-][a-z]/i) return { web: "Channel mode toggle", phase: "2", notes: nil } end if cmd.match?(/\bauser\b|\bruser\b/i) return { web: "Trust level list", phase: "2", notes: nil } end if cmd.match?(/\bdns\b/i) return { web: "DNS lookup tool", phase: "3", notes: nil } end if cmd.match?(/\blog\b/i) return { web: "Open/download query log", phase: "2", notes: nil } end { web: "Turbo context action", phase: "2", notes: "Review manually" } end
Source
# File lib/directchat_mirc/inventory_report.rb, line 106 def event_handlers_section(handlers_by_file) priority = %w[style.ibis raw.ibis events.ibis input.ibis protection.ibis] section = [ "## 4. Event handlers (summary)", "", "Grouped by file. Formatter details: [irc-client-foundations.md](irc-client-foundations.md) §19 and PRD §12.3 (raw numerics).", "" ] (priority + handlers_by_file.keys.sort).uniq.each do |file| handlers = handlers_by_file[file] next if handlers.nil? || handlers.empty? section << "### `#{file}`" section << "" handlers.first(25).each { |h| section << "- `#{h}`" } section << "- _… #{handlers.length - 25} more_" if handlers.length > 25 section << "" end section end
Source
# File lib/directchat_mirc/extractors.rb, line 12 def extract_aliases(body) names = [] body.each_line do |line| stripped = line.strip next if stripped.empty? || stripped.start_with?(";") if (match = stripped.match(/\Aalias\s+(-\w+\s+)?(\S+)/i)) names << match[2].downcase elsif (match = stripped.match(/\A([a-zA-Z_][\w]*)\s+\{/)) names << match[1].downcase elsif (match = stripped.match(/\A([a-zA-Z_][\w]*)\s+\S/)) && !stripped.match?(/\Aon\s/i) names << match[1].downcase end end names.uniq end
Source
# File lib/directchat_mirc/extractors.rb, line 39 def extract_all_popups(contents) contents.flat_map do |filename, body| next [] unless filename.start_with?("popups_") menu = filename.sub(/\Apopups_/, "").sub(/\.ibis\z/i, "") extract_popup_items(menu, body) end end
Source
# File lib/directchat_mirc/extractors.rb, line 29 def extract_on_handlers(body) body.each_line.filter_map do |line| stripped = line.strip next if stripped.empty? || stripped.start_with?(";") next unless stripped.match?(/\Aon\s+\^?\d*:/i) stripped.sub(/\s*\{.*\z/, "").slice(0, 120) end end
Source
# File lib/directchat_mirc/extractors.rb, line 48 def extract_popup_items(menu, body) items = [] body.each_line do |line| next unless popup_item_line?(line) stripped = line.strip if (match = stripped.match(/\A\.([^:]+):\s*(.*)\z/i)) items << build_popup_row(menu: menu, label: match[1].strip, command: match[2].strip) elsif (match = stripped.match(/\A([^.:][^:]*):\s*(.*)\z/)) items << build_popup_row(menu: menu, label: match[1].strip, command: match[2].strip) end end items end
Source
# File lib/directchat_mirc/extractors.rb, line 138 def extract_vars(body) body.each_line.filter_map do |line| stripped = line.strip next if stripped.empty? || stripped.start_with?(";") next unless (match = stripped.match(/\A%([a-zA-Z0-9_]+)\s+(.+)\z/)) name = match[1] value = match[2].strip next if name.match?(VAR_SKIP_PATTERN) { name: name, value: value, web_key: VAR_WEB_MAP[name] } end end
Source
# File lib/directchat_mirc/inventory_report.rb, line 61 def file_inventory_section(rows) section = [ "## 1. File inventory", "", "| File | Purpose | Lines | Aliases | Handlers | Phase | Portable |", "|------|---------|-------|---------|----------|-------|----------|" ] rows.each do |row| section << "| `#{row[:name]}` | #{row[:purpose]} | #{row[:lines]} | #{row[:aliases]} | #{row[:handlers]} | #{row[:phase]} | #{row[:portable]} |" end section << "" end
Source
# File lib/directchat_mirc/inventory_report.rb, line 36 def format_reply(path:, file_count:, popup_count:) rel = path.to_s.sub(%r{\A.*/docs/}, "docs/") meta = [ "#{file_count} scripts", "#{popup_count} popup items", "#{TOP_ALIAS_NOTES.length} curated aliases" ].join(" · ") [ IrcFormat.report_header(tag: "DCHAT", title: "Ibis script reference written", style: IrcFormat.color(0, 4)), "Saved script inventory to #{rel}.", IrcFormat.report_footer(meta) ].join("\n") end
Source
# File lib/directchat_mirc/extractors.rb, line 63 def popup_item_line?(line) stripped = line.strip return false if stripped.empty? return false if stripped.start_with?(";") return false if stripped.start_with?("-") return false if stripped.start_with?("$menuHeading") return false if stripped.start_with?("$style") return false if stripped.match?(/\A--/) stripped.include?(":") end
Source
# File lib/directchat_mirc/inventory_report.rb, line 89 def popup_section(popups) section = [ "## 3. Popup menu → web mapping", "", "Extracted from `popups_*.ibis` (#{popups.length} items).", "", "| Menu | Label | IRC action | Web implementation | Phase | Notes |", "|------|-------|--------------|-------------------|-------|-------|" ] popups.each do |row| notes = row[:notes] || "" cmd = row[:command].gsub("|", "\\|").slice(0, 60) section << "| #{row[:menu]} | #{row[:label]} | `#{cmd}` | #{row[:web]} | #{row[:phase]} | #{notes} |" end section << "" end
Source
# File lib/directchat_mirc/extractors.rb, line 156 def safe_read(path) DirectchatMircParse.safe_read(path) end
Source
# File lib/directchat_mirc/inventory_report.rb, line 74 def top_aliases_section(rows) section = [ "## 2. Top aliases (curated)", "", "Maps the most important Ibis aliases to web client behaviour. See [irc-client-foundations.md](irc-client-foundations.md) §19 for event formatting.", "", "| Alias | File | Summary | Web equivalent | Phase |", "|-------|------|---------|----------------|-------|" ] rows.each do |row| section << "| `#{row[:name]}` | `#{row[:file]}` | #{row[:summary]} | #{row[:web]} | #{row[:phase]} |" end section << "" end
Source
# File lib/directchat_mirc/inventory_report.rb, line 127 def vars_section(vars) mapped, unmapped = vars.partition { |v| v[:web_key] } section = [ "## 5. Config variables (`vars.ibis`)", "", "Colour and behaviour defaults mapped to web settings. Bnc/mp3/xdcc vars omitted.", "", "| Ibis var | Default | Web setting |", "|----------|---------|-------------|" ] mapped.each do |row| section << "| `%#{row[:name]}` | #{row[:value].slice(0, 40)} | `#{row[:web_key]}` |" end section << "" section << "### Unmapped vars (reference only)" section << "" unmapped.first(30).each do |row| section << "- `%#{row[:name]}` = #{row[:value].slice(0, 50)}" end section << "- _… #{unmapped.length - 30} more_" if unmapped.length > 30 section << "" end
Public Instance Methods
Source
# File lib/directchat/mirc_inventory.rb, line 17 def analyze(root:) root = File.expand_path(root.to_s) catalog = DirectchatMircCatalog.analyze(root: root) script_dirs = DirectchatMircCatalog.resolve_script_dirs(root) files = DirectchatMircCatalog.collect_files(root, script_dirs) contents = files.transform_values { |path| safe_read(path) } aliases_by_file = contents.transform_values { |body| extract_aliases(body) } handlers_by_file = contents.transform_values { |body| extract_on_handlers(body) } popups = extract_all_popups(contents) vars = contents["vars.ibis"] ? extract_vars(contents["vars.ibis"]) : [] alias_locations = {} aliases_by_file.each do |file, names| names.each { |name| alias_locations[name] ||= file } end { catalog: catalog, files: files, file_rows: build_file_rows(files, contents, aliases_by_file, handlers_by_file), top_aliases: build_top_alias_rows(alias_locations, contents), popups: popups, vars: vars, handlers_by_file: handlers_by_file } end
Source
# File lib/directchat/mirc_inventory.rb, line 45 def build(root:) data = analyze(root: root) lines = [] lines << "# Ibis Script Reference" lines << "" lines << "**Generated from:** `reference/stuff` · #{data[:files].length} script files" lines << "**Companion docs:** [pdf.md](pdf.md) (feature PRD) · [irc-client-foundations.md](irc-client-foundations.md)" lines << "**Syntax reference:** `reference/mirc.pdf`" lines << "" lines << "Structured inventory for web client implementation. Raw `.ibis` files are the source of truth for edge cases; this doc maps behaviour to Rails/Hotwire equivalents." lines << "" lines << "---" lines << "" lines.concat(file_inventory_section(data[:file_rows])) lines << "---" lines << "" lines.concat(top_aliases_section(data[:top_aliases])) lines << "---" lines << "" lines.concat(popup_section(data[:popups])) lines << "---" lines << "" lines.concat(event_handlers_section(data[:handlers_by_file])) lines << "---" lines << "" lines.concat(vars_section(data[:vars])) lines << "" lines << "**End of script reference.** Regenerate with `!directchat inventory`." lines << "" lines.join("\n") end
Source
# File lib/directchat/mirc_inventory.rb, line 91 def output_path(project_root:) File.join(File.expand_path(project_root.to_s), "docs", OUTPUT_FILENAME) end
Source
# File lib/directchat/mirc_inventory.rb, line 77 def write(project_root:, catalog: nil) root = File.expand_path(project_root.to_s) data = analyze(root: root) path = output_path(project_root: root) FileUtils.mkdir_p(File.dirname(path)) File.write(path, build(root: root)) { path: path, catalog: catalog || data[:catalog], file_count: data[:files].length, popup_count: data[:popups].length } end