Class: BitClust::IncludeGraph

Inherits:
Object
  • Object
show all
Defined in:
lib/bitclust/include_graph.rb

Overview

doctree の #@include グラフを版ゲート付きで解析する。

LIBRARIES の各エントリ をルートファイル .rd として、 #@include(target) を include 元ディレクトリ相対で解決しながら再帰的に走査し、 各ファイルの分類(grouping = エンティティ / fragment = 共有断片)と、 grouping ファイルの全所属(ライブラリ + 経路上の版条件)を生のまま収集する。

特定の版範囲([3.0, 4.2) 等)への絞り込みは行わない faithful 層。 スコープ適用は IncludeGraph::Scope が担い、範囲はパラメータ化されている (旧版サルベージ時に別範囲で再利用するため)。

Defined Under Namespace

Classes: Condition, Membership, Scope

Constant Summary collapse

ENTITY_H1_RE =

RRDParser は /\A=[^=]/ で H1 行を認識し = 直後の空白は必須でない (実データ: _builtin/Encoding は「=class Encoding」)

/\A=(?!=)\s*(?:class|module|object|reopen|redefine)\b/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(src_root) ⇒ IncludeGraph

Returns a new instance of IncludeGraph.



154
155
156
157
158
159
160
161
# File 'lib/bitclust/include_graph.rb', line 154

def initialize(src_root)
  @src_root = src_root
  @memberships = {}     # relpath => [Membership]
  @kinds = {}           # relpath => :grouping | :fragment
  @grouping_sites = {}  # relpath => [include target(記載どおりの文字列)]
  @library_gates = {}   # library name => [Condition](LIBRARIES 由来)
  @warnings = []
end

Instance Attribute Details

#warningsObject (readonly)

Returns the value of attribute warnings.



152
153
154
# File 'lib/bitclust/include_graph.rb', line 152

def warnings
  @warnings
end

Class Method Details

.analyze(src_root) ⇒ Object



27
28
29
# File 'lib/bitclust/include_graph.rb', line 27

def self.analyze(src_root)
  new(src_root).analyze
end

.bounds(conditions) ⇒ Object

conditions から下限・上限を [Gem::Version, 原文字列] の組で集める。 :if の連言(ubygems の "1.9.1" <= version and version < "2.5.0" 等)も 分解して境界に寄与させる。分解できない連言子は寄与しない(保守的に広く扱う。 恒偽の証明は Scope#never? が連言子単位で別途行う)



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/bitclust/include_graph.rb', line 42

def self.bounds(conditions)
  sinces = [] #: Array[bound]
  untils = [] #: Array[bound]
  conditions.each do |c|
    case c.kind
    when :since then sinces << [Gem::Version.new(c.version), c.version]
    when :until then untils << [Gem::Version.new(c.version), c.version]
    when :if
      if_bounds(c.version).each do |kind, v|
        (kind == :since ? sinces : untils) << [Gem::Version.new(v), v]
      end
    end
  end
  [sinces.max_by(&:first), untils.min_by(&:first)]
end

.if_bounds(expr) ⇒ Object

連言(and 区切り)の #@if 条件式を since/until 境界に分解する。 対応形: "X" <= version / version >= "X" → since X、version < "X" → until X。 <= や否定(#@else 反転)は正確に表現できないので分解しない



61
62
63
64
65
66
67
68
69
70
71
# File 'lib/bitclust/include_graph.rb', line 61

def self.if_bounds(expr)
  return [] if expr.lstrip.start_with?('!')
  bounds = [] #: Array[[Symbol, String?]]
  expr.split(/\band\b/).each do |c|
    case c
    when /version\s*<\s*"([\d.]+)"/ then bounds << [:until, $1]
    when /version\s*>=\s*"([\d.]+)"/, /"([\d.]+)"\s*<=\s*version/ then bounds << [:since, $1]
    end
  end
  bounds
end

.interval(conditions) ⇒ Object

conditions が表す版区間 [lo, hi)。lo/hi は Gem::Version、制約なしは nil。 :since は max、:until は min を取る。



33
34
35
36
# File 'lib/bitclust/include_graph.rb', line 33

def self.interval(conditions)
  lo, hi = bounds(conditions)
  [lo&.first, hi&.first]
end

Instance Method Details

#analyzeObject



163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/bitclust/include_graph.rb', line 163

def analyze
  read_libraries.each do |name, gate|
    root = "#{name}.rd"
    unless File.file?(File.join(@src_root, root))
      @warnings << "library root not found: #{root}"
      next
    end
    @library_gates[name] = gate
    walk(root, name, gate, [root])
  end
  self
end

#fragmentsObject



186
187
188
# File 'lib/bitclust/include_graph.rb', line 186

def fragments
  @kinds.select { |_, kind| kind == :fragment }.keys.sort
end

#front_matter_map(scope) ⇒ Object

各 grouping メンバーへ注入する front matter(スコープ適用済み)。 単一所属: { relpath => { "library" => name, "since" => v, "until" => v } } 複数所属: { relpath => { "library" => [{ "name" => n, "since" => v, "until" => v }, ...], ... } } RRDToMarkdown の extra_front_matter: にそのまま渡せる形。

  • スコープ外のメンバーは含まない(旧版サルベージは別スコープで再実行)
  • 複数ライブラリへの所属(旧 rdoc の同時所属、thread 系の版切替所属)は ゲート付きリストで表現する(MARKUP_SPEC §1.2)。並び順は 「現在まで存在する側(until なし)→ 名前順」で決定的にする。 このときトップレベルの since/until はライブラリ横断の hull (エンティティ自体の存在ゲート)
  • 同一ライブラリ内の複数 include サイトは、いずれかが有効なら エンティティが存在するため、ゲートは区間の hull(弱い方)を取る


229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/bitclust/include_graph.rb', line 229

def front_matter_map(scope)
  result = {} #: Hash[String, front_matter]
  groupings.each do |path, ms|
    covered = ms.select { |m| scope.cover?(m.conditions) }
    next if covered.empty?
    libraries = covered.map(&:library).uniq
    per_lib = libraries.to_h { |lib|
      [lib, hull(covered.select { |m| m.library == lib }
                        .map { |m| scope.gate(m.conditions) })]
    }
    if libraries.size == 1
      fm = { 'library' => libraries.first }
      gate = per_lib.values.first
    else
      entries = per_lib.map { |lib, g|
        e = { 'name' => lib }
        e['since'] = g[:since] if g[:since]
        e['until'] = g[:until] if g[:until]
        e
      }.sort_by { |e| [e['until'] ? 1 : 0, e['name']] }
      fm = { 'library' => entries }
      gate = hull(per_lib.values)
    end
    fm['since'] = gate[:since] if gate[:since]
    fm['until'] = gate[:until] if gate[:until]
    result[path] = fm
  end
  result
end

#grouping_include_sitesObject

grouping include のサイト一覧(prune 対象)。 { relpath => [#@include に記載どおりの target 文字列] } fragment include は含まない(transclusion として温存する)。



193
194
195
# File 'lib/bitclust/include_graph.rb', line 193

def grouping_include_sites
  @grouping_sites
end

#groupingsObject



181
182
183
184
# File 'lib/bitclust/include_graph.rb', line 181

def groupings
  @kinds.select { |_, kind| kind == :grouping }.keys.sort
         .to_h { |path| [path, @memberships.fetch(path, [])] }
end

#library_front_matter_map(scope) ⇒ Object

各ライブラリ概要ファイル(LIBRARIES のルート)へ注入する front matter。 { relpath => { "type" => "library", "since" => v, "until" => v } } LIBRARIES 内の版ゲート(fiber: until 3.1 等)をスコープ適用して付与する。 スコープ外のライブラリ(cmath/scanf/sync 等)は含まない。



201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/bitclust/include_graph.rb', line 201

def library_front_matter_map(scope)
  result = {} #: Hash[String, Hash[String, String]]
  @library_gates.each do |name, gate|
    root = "#{name}.rd"
    scoped = scope.gate(gate)
    next unless scoped
    fm = { 'type' => 'library' }
    fm['since'] = scoped[:since] if scoped[:since]
    fm['until'] = scoped[:until] if scoped[:until]
    result[root] = fm
  end
  result
end

#memberships(relpath) ⇒ Object

grouping ファイル relpath の全 membership(生・スコープ未適用)



177
178
179
# File 'lib/bitclust/include_graph.rb', line 177

def memberships(relpath)
  @memberships.fetch(relpath, [])
end