|
| 1 | +require 'json' |
| 2 | +require 'safe_yaml' |
| 3 | + |
| 4 | +# Generate JSON lookups of all actions, triggers, and conditions (with |
| 5 | +# their data fields) for use by prism-doc-links.js. For each entry the |
| 6 | +# lookup exposes: |
| 7 | +# |
| 8 | +# d = description (from front matter, used as fallback tooltip text) |
| 9 | +# u = URL of the page |
| 10 | +# t = title (for fallback tooltip text) |
| 11 | +# p = parameters map { field_name: short_description }, extracted from |
| 12 | +# the first `{% fields %}` or `{% fields_yaml %}` block in the page |
| 13 | +# content. The former carries both UI and YAML sub-maps; we pick the |
| 14 | +# `yaml:` sub-map. The latter is a standalone YAML-only block. |
| 15 | +# |
| 16 | +# Three separate data keys are emitted, so the client JS can look up each |
| 17 | +# kind in its own namespace: |
| 18 | +# site.data.actions_json keyed by "<domain>.<name>" |
| 19 | +# site.data.triggers_json keyed by "<domain>.<name>" |
| 20 | +# site.data.conditions_json keyed by "<domain>.<name>" |
| 21 | +module Jekyll |
| 22 | + class DocCollectionsDataGenerator < Generator |
| 23 | + safe true |
| 24 | + priority :low |
| 25 | + |
| 26 | + COLLECTIONS = [ |
| 27 | + { name: 'actions', front_matter_key: 'action', data_key: 'actions_json' }, |
| 28 | + { name: 'triggers', front_matter_key: 'trigger', data_key: 'triggers_json' }, |
| 29 | + { name: 'conditions', front_matter_key: 'condition', data_key: 'conditions_json' } |
| 30 | + ].freeze |
| 31 | + |
| 32 | + OPTIONS_YAML_PATTERN = /\{%\s*options_yaml\s*%\}(.*?)\{%\s*endoptions_yaml\s*%\}/m |
| 33 | + MAX_FIELD_DESCRIPTION_LENGTH = 160 |
| 34 | + |
| 35 | + def generate(site) |
| 36 | + COLLECTIONS.each do |info| |
| 37 | + entries = {} |
| 38 | + site.collections[info[:name]]&.docs&.each do |doc| |
| 39 | + name = doc.data[info[:front_matter_key]] |
| 40 | + next unless name |
| 41 | + |
| 42 | + entry = { |
| 43 | + 'd' => doc.data['description'].to_s, |
| 44 | + 'u' => doc.url, |
| 45 | + 't' => doc.data['title'].to_s |
| 46 | + } |
| 47 | + |
| 48 | + params = extract_fields(doc, name) |
| 49 | + entry['p'] = params if params && !params.empty? |
| 50 | + |
| 51 | + entries[name] = entry |
| 52 | + end |
| 53 | + |
| 54 | + # Escape </ to prevent breaking out of <script> tags when embedded in HTML |
| 55 | + site.data[info[:data_key]] = JSON.generate(entries).gsub('</', '<\\/') |
| 56 | + end |
| 57 | + end |
| 58 | + |
| 59 | + private |
| 60 | + |
| 61 | + # Extract option descriptions from the page's `{% options_yaml %}` block. |
| 62 | + def extract_fields(doc, entry_name) |
| 63 | + match = doc.content.match(OPTIONS_YAML_PATTERN) |
| 64 | + return nil unless match |
| 65 | + |
| 66 | + yaml_body = SafeYAML.load(match[1]) |
| 67 | + return nil unless yaml_body.is_a?(Hash) |
| 68 | + |
| 69 | + fields = {} |
| 70 | + yaml_body.each do |name, config| |
| 71 | + next unless config.is_a?(Hash) && config['description'] |
| 72 | + desc = config['description'].to_s.strip.gsub(/\s+/, ' ') |
| 73 | + # Truncate to first sentence if too long for a tooltip. |
| 74 | + desc = desc.split(/(?<=\.)\s/)[0] if desc.length > MAX_FIELD_DESCRIPTION_LENGTH |
| 75 | + fields[name] = desc |
| 76 | + end |
| 77 | + fields |
| 78 | + rescue => e |
| 79 | + Jekyll.logger.warn "DocCollectionsData:", "Failed to parse fields for #{entry_name}: #{e.message}" |
| 80 | + nil |
| 81 | + end |
| 82 | + end |
| 83 | +end |
0 commit comments