Module: Twin::Preview

Defined in:
lib/twin/preview.rb

Overview

Compact markdown preview for a single sync block: frontmatter + intro (text before the first heading) + the heading section that contains the YAML block for target_path.

Constant Summary collapse

YAML_FENCE =
/\A```ya?ml\s*\z/.freeze
FENCE_END =
/\A```\s*\z/.freeze
HEADING =
/\A(\#{1,6})\s/.freeze
H2_PLUS =
/\A\#{2,6}\s/.freeze
PATH_KEY =
/\APath:\s*['"]?([^'"]+?)['"]?\s*\z/.freeze

Class Method Summary collapse

Class Method Details

.extract_compact(file_path, target_path) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/twin/preview.rb', line 14

def extract_compact(file_path, target_path)
  content = File.read(file_path, encoding: "UTF-8")

  frontmatter, body = split_frontmatter(content)
  lines = body.lines.map(&:chomp)

  intro     = extract_intro(lines)
  section   = extract_section_for(lines, target_path)

  [frontmatter, intro, section].reject(&:empty?).join("\n\n") + "\n"
rescue Errno::ENOENT
  ""
end

.extract_intro(lines) ⇒ Object

Intro = everything up to the first H2 (H1 + body counts as document title).



36
37
38
39
40
# File 'lib/twin/preview.rb', line 36

def extract_intro(lines)
  idx = lines.index { |l| l =~ H2_PLUS }
  idx = lines.size unless idx
  lines[0...idx].join("\n").strip
end

.extract_section_for(lines, target) ⇒ Object

Locate the YAML block whose Path: matches target, then return the surrounding heading section (nearest preceding heading until the next heading of equal-or-higher level).



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/twin/preview.rb', line 45

def extract_section_for(lines, target)
  block_start, block_end = find_block(lines, target)
  return "" unless block_start

  section_start = block_start
  (block_start - 1).downto(0) do |i|
    if lines[i] =~ HEADING
      section_start = i
      break
    end
  end

  section_end = lines.size - 1
  if (m = lines[section_start]&.match(HEADING))
    level = m[1].length
    ((block_end + 1)...lines.size).each do |i|
      if (m2 = lines[i].match(HEADING)) && m2[1].length <= level
        section_end = i - 1
        break
      end
    end
  end

  lines[section_start..section_end].join("\n").strip
end

.find_block(lines, target) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/twin/preview.rb', line 71

def find_block(lines, target)
  i = 0
  while i < lines.size
    if lines[i] =~ YAML_FENCE
      k = i + 1
      matched = false
      while k < lines.size && lines[k] !~ FENCE_END
        if (m = lines[k].match(PATH_KEY)) && m[1] == target
          matched = true
        end
        k += 1
      end
      return [i, k] if matched
      i = k + 1
    else
      i += 1
    end
  end
  nil
end

.split_frontmatter(content) ⇒ Object



28
29
30
31
32
33
# File 'lib/twin/preview.rb', line 28

def split_frontmatter(content)
  return ["", content] unless content.start_with?("---\n") || content.start_with?("---\r\n")
  parts = content.split(/^---\s*$\n/, 3)
  return ["", content] if parts.size < 3
  ["---\n#{parts[1]}---", parts[2]]
end