Module: BitClust::IncludePruner

Defined in:
lib/bitclust/include_pruner.rb

Overview

RD テキストから指定 target の #@include 行を除去する純変換(rd→rd)。

新パイプラインでは grouping include(エンティティの取り込み)は glob + front matter による発見に置き換わるため、ライブラリ概要ファイル等から 除去する(二重取り込み防止)。fragment include(共有断片の transclusion)は target に含めなければそのまま残る。

除去に伴う後始末:

  • 除去で空になった版ゲートブロック(#@since/#@until/#@if ... #@end、 #@else の両枝が空の場合を含む)はブロックごと除去する
  • 除去痕の前後が空行同士なら1つに畳む。ファイル先頭に残る空行は削る (末尾の空行は保持する: メタデータ領域再生成の空行仕様と整合させるため)
  • もともと空だったブロックや、target を含まない入力はバイト単位で変更しない

Constant Summary collapse

INCLUDE_RE =
/\A\#@include\s*\((.*?)\)/
GATE_OPEN_RE =
/\A\#@(?:since|until|if)\b/
CODE_OPEN_RE =
/\A\#@samplecode\b/
ELSE_RE =
/\A\#@else\b/
END_RE =
/\A\#@end\b/
BLANK_RE =
/\A\s*\z/

Class Method Summary collapse

Class Method Details

.blank_only?(stream) ⇒ Boolean

Returns:

  • (Boolean)


120
121
122
# File 'lib/bitclust/include_pruner.rb', line 120

def blank_only?(stream)
  stream.all? { |item| item.equal?(REMOVED) || item =~ BLANK_RE }
end

.emit(nodes, lookup, stream) ⇒ Object

nodes を stream(行と REMOVED 番兵の列)へ展開し、変更の有無を返す



83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/bitclust/include_pruner.rb', line 83

def emit(nodes, lookup, stream)
  changed = false
  nodes.each do |node|
    if node.is_a?(Block)
      changed |= emit_block(node, lookup, stream)
    elsif node =~ INCLUDE_RE && lookup[$1]
      stream << REMOVED
      changed = true
    else
      stream << node
    end
  end
  changed
end

.emit_block(block, lookup, stream) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/bitclust/include_pruner.rb', line 98

def emit_block(block, lookup, stream)
  body = [] #: stream
  body_changed = emit(block.body, lookup, body)
  else_body = [] #: stream
  else_changed = block.else_line ? emit(block.else_body, lookup, else_body) : false
  changed = body_changed || else_changed

  if block.gate && changed && blank_only?(body) && blank_only?(else_body)
    stream << REMOVED
    return true
  end

  stream << block.open
  stream.concat(body)
  if block.else_line
    stream << block.else_line
    stream.concat(else_body)
  end
  stream << block.end_line
  changed
end

.parse(lines) ⇒ Object

lines をブロック構造にパースする。対応の取れないファイルは throw で nil を返す



50
51
52
53
54
# File 'lib/bitclust/include_pruner.rb', line 50

def parse(lines)
  nodes, i = parse_nodes(lines, 0)
  throw :unbalanced, nil if i < lines.length   # トップレベルに #@else / #@end
  nodes
end

.parse_nodes(lines, i) ⇒ Object



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
# File 'lib/bitclust/include_pruner.rb', line 56

def parse_nodes(lines, i)
  nodes = [] #: Array[node]
  while i < lines.length
    line = lines[i]
    if line =~ ELSE_RE || line =~ END_RE
      return [nodes, i]
    elsif line =~ GATE_OPEN_RE || line =~ CODE_OPEN_RE
      gate = line !~ CODE_OPEN_RE
      body, j = parse_nodes(lines, i + 1)
      else_line = nil
      else_body = [] #: Array[node]
      if j < lines.length && lines[j] =~ ELSE_RE
        else_line = lines[j]
        else_body, j = parse_nodes(lines, j + 1)
      end
      throw :unbalanced, nil unless j < lines.length && lines[j] =~ END_RE
      nodes << Block.new(line, body, else_line, else_body, lines[j], gate)
      i = j + 1
    else
      nodes << line
      i += 1
    end
  end
  [nodes, i]
end

.prune(src, targets) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/bitclust/include_pruner.rb', line 34

def prune(src, targets)
  return src if targets.empty?
  lookup = {} #: Hash[String?, bool]
  targets.each { |t| lookup[t] = true }

  lines = src.lines
  nodes = catch(:unbalanced) { parse(lines) }
  return src if nodes.nil?

  stream = [] #: stream
  changed = emit(nodes, lookup, stream)
  return src unless changed
  tidy(stream).join
end

.tidy(stream) ⇒ Object

REMOVED 番兵を取り除きつつ、除去痕の前後が空行同士なら1つに畳み、 先頭に残った空行を削る



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/bitclust/include_pruner.rb', line 126

def tidy(stream)
  result = [] #: Array[String]
  pending = false
  stream.each do |item|
    if item.equal?(REMOVED)
      pending = true
      next
    end
    if pending && item =~ BLANK_RE && (result.empty? || result.last =~ BLANK_RE)
      pending = false
      next
    end
    pending = false
    result << item
  end
  result
end