Class: Hoe::Halostatue::Markdown::Linkify

Inherits:
Object
  • Object
show all
Defined in:
lib/hoe/halostatue/markdown/linkify.rb

Overview

:nodoc:

Constant Summary collapse

USERNAME_PATTERN =

Match GitHub username (@username) but not email addresses or Mastodon handles Basic pattern - additional validation done post-match

%r{
  (?<![A-Za-z0-9])      # Not preceded by alphanumeric (prevents email match)
  @                      # Literal @ sign
  ([A-Za-z0-9-]{1,39})   # 1-39 chars of alphanumeric or hyphen
  (?![-A-Za-z0-9@])      # Not followed by alphanumeric, hyphen, or @ (prevents Mastodon/email)
}x

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bug_tracker_uri: nil, style: :reference, uri_prefixes: {}) ⇒ Linkify

Returns a new instance of Linkify.



48
49
50
51
52
53
# File 'lib/hoe/halostatue/markdown/linkify.rb', line 48

def initialize(bug_tracker_uri: nil, style: :reference, uri_prefixes: {})
  @bug_tracker_uri = bug_tracker_uri
  @repo_owner, @repo_name = extract_repo_info(bug_tracker_uri)
  @style = style
  @uri_prefixes = self.class.normalize_uri_prefixes(uri_prefixes)
end

Class Method Details

.linkify(source) ⇒ Object



27
28
29
# File 'lib/hoe/halostatue/markdown/linkify.rb', line 27

def self.linkify(source, ...)
  new(...).linkify(source)
end

.linkify_file(filename) ⇒ Object



23
24
25
# File 'lib/hoe/halostatue/markdown/linkify.rb', line 23

def self.linkify_file(filename, ...)
  new(...).linkify(File.read(filename))
end

.normalize_uri_prefixes(prefixes) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/hoe/halostatue/markdown/linkify.rb', line 31

def self.normalize_uri_prefixes(prefixes)
  case prefixes
  when true
    {issue: "issue", pull: "pull request", discussion: "discussion"}
  when false, nil
    {}
  else
    extra = prefixes.keys - %i[issue pull discussion]

    if extra.empty?
      prefixes
    else
      raise ArgumentError, "Extra keys for markdown_linkify_uri_prefixes: #{extra.inspect}"
    end
  end
end

Instance Method Details

#linkify(source) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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
# File 'lib/hoe/halostatue/markdown/linkify.rb', line 55

def linkify(source)
  scan_existing_references(source) => {existing_by_key:, existing_by_ref:}
  new_by_key = {}
  new_by_ref = {}
  replacements = []
  processed_positions = Set.new

  doc = Kramdown::Document.new(source, input: "GFM")

  walk(doc.root) do |element, ancestors|
    next unless element.type == :text
    next if in_skip_context?(ancestors)

    text = element.value

    # Find this text occurrence in source that we haven't processed yet
    source_offset = 0
    while (text_pos = source.index(text, source_offset))
      break if processed_positions.include?(text_pos)

      # Mark this position as processed
      processed_positions.add(text_pos)

      # Scan for patterns in this text occurrence
      scan_usernames(source, text, text_pos, replacements, existing_by_key, existing_by_ref, new_by_key, new_by_ref)
      scan_owner_repo_issues(source, text, text_pos, replacements, existing_by_key, existing_by_ref, new_by_key, new_by_ref)
      scan_issue_mentions(source, text, text_pos, replacements, existing_by_key, existing_by_ref, new_by_key, new_by_ref)
      scan_github_uris(source, text, text_pos, replacements, existing_by_key, existing_by_ref, new_by_key, new_by_ref)
      scan_github_repo_uris(source, text, text_pos, replacements, existing_by_key, existing_by_ref, new_by_key, new_by_ref)
      scan_github_user_uris(source, text, text_pos, replacements, existing_by_key, existing_by_ref, new_by_key, new_by_ref)

      break # Only process first unprocessed occurrence
    end
  end

  return {markdown: source, changed: false} if replacements.empty?

  # Apply replacements in reverse
  result = source.dup

  replacements.reverse_each do |r|
    result[r[:start]...r[:end]] = r[:replacement]
  end

  # Append new references if using reference style
  if @style == :reference && !new_by_key.empty?
    result = append_references(result, new_by_key)
  end

  {markdown: result, changed: true}
end