Class: BitClust::MethodDatabase

Inherits:
Database show all
Includes:
Completion
Defined in:
lib/bitclust/methoddatabase.rb

Overview

Database for Ruby entries (library, class, method).

Constant Summary

Constants included from NameUtils

NameUtils::CHAR_TO_MARK, NameUtils::CHAR_TO_NAME, NameUtils::CLASS_NAME_RE, NameUtils::CLASS_PATH_RE, NameUtils::CONST_PATH_RE, NameUtils::CONST_RE, NameUtils::GVAR_RE, NameUtils::LIBNAME_RE, NameUtils::MARK_TO_CHAR, NameUtils::MARK_TO_NAME, NameUtils::METHOD_NAME_RE, NameUtils::METHOD_SPEC_RE, NameUtils::MID, NameUtils::NAME_TO_CHAR, NameUtils::NAME_TO_MARK, NameUtils::TYPEMARK_RE

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Database

#atomic_write_open, #dummy?, #encoding, #entries, #exist?, #foreach_line, #load_properties, #makepath, #properties, #propget, #propkeys, #propset, #read, #save_properties, #transaction

Methods included from NameUtils

build_method_id, classid2name, classname2id, classname?, decodeid, decodename_fs, decodename_url, display_typemark, encodeid, encodename_fs, encodename_rdocurl, encodename_url, functionname?, gvarname?, html_filename, libid2name, libname2id, libname?, method_spec?, methodid2classid, methodid2libid, methodid2mname, methodid2specparts, methodid2specstring, methodid2typechar, methodid2typemark, methodid2typename, methodname?, split_method_id, split_method_spec, typechar2mark, typechar2name, typechar?, typemark2char, typemark2name, typemark?, typename2char, typename2mark, typename?

Constructor Details

#initialize(prefix) ⇒ MethodDatabase

Returns a new instance of MethodDatabase.



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

def initialize(prefix)
  super prefix
  @librarymap = nil
  @classmap = {}
  @class_extent_loaded = false
  @in_transaction = false
  @dirty_libraries = {}
  @dirty_classes = {}
  @dirty_methods = {}
  @refs = nil
end

Instance Attribute Details

#refsObject



221
222
223
# File 'lib/bitclust/methoddatabase.rb', line 221

def refs
  @refs ||= RefsDatabase.load(realpath('refs'))
end

Instance Method Details

#classesObject

Classe Entry



380
381
382
# File 'lib/bitclust/methoddatabase.rb', line 380

def classes
  classmap().values
end

#clear_dirtyObject



127
128
129
130
131
# File 'lib/bitclust/methoddatabase.rb', line 127

def clear_dirty
  @dirty_libraries.clear
  @dirty_classes.clear
  @dirty_methods.clear
end

#copy_docObject



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/bitclust/methoddatabase.rb', line 234

def copy_doc
  return copy_doc_md if @md_root
  return unless @root
  root_path = Pathname.new(@root).expand_path
  Dir.glob("#{@root}/../../doc/**/*.rd").each do |f|
    if %r!\A#{Regexp.escape(@root)}/\.\./\.\./doc/([-\./\w]+)\.rd\z! =~ f
      id = libname2id($1 || raise)
      se = DocEntry.new(self, id)
      s = Preprocessor.read(f, properties)
      title, source = RRDParser.split_doc(s)
      relative_path = Pathname.new(f).expand_path(@root).relative_path_from(root_path)
      se.title = title
      se.source = source
      se.source_location = Location.new(relative_path, 1)
      se.save
    end
  end
end

#dirty?Boolean

Returns:

  • (Boolean)


91
92
93
94
95
# File 'lib/bitclust/methoddatabase.rb', line 91

def dirty?
  not @dirty_libraries.empty? or
  not @dirty_classes.empty? or
  not @dirty_methods.empty?
end

#dirty_class(c) ⇒ Object



111
112
113
# File 'lib/bitclust/methoddatabase.rb', line 111

def dirty_class(c)
  @dirty_classes[c] = true
end

#dirty_library(lib) ⇒ Object



103
104
105
# File 'lib/bitclust/methoddatabase.rb', line 103

def dirty_library(lib)
  @dirty_libraries[lib] = true
end

#dirty_method(m) ⇒ Object



119
120
121
# File 'lib/bitclust/methoddatabase.rb', line 119

def dirty_method(m)
  @dirty_methods[m] = true
end

#docsObject

Doc Entry



316
317
318
# File 'lib/bitclust/methoddatabase.rb', line 316

def docs
  docmap().values
end

#each_dirty_class(&block) ⇒ Object



115
116
117
# File 'lib/bitclust/methoddatabase.rb', line 115

def each_dirty_class(&block)
  @dirty_classes.each_key(&block)
end

#each_dirty_entry(&block) ⇒ Object



97
98
99
100
101
# File 'lib/bitclust/methoddatabase.rb', line 97

def each_dirty_entry(&block)
  (@dirty_libraries.keys +
   @dirty_classes.keys +
   @dirty_methods.keys).each(&block)
end

#each_dirty_library(&block) ⇒ Object



107
108
109
# File 'lib/bitclust/methoddatabase.rb', line 107

def each_dirty_library(&block)
  @dirty_libraries.each_key(&block)
end

#each_dirty_method(&block) ⇒ Object



123
124
125
# File 'lib/bitclust/methoddatabase.rb', line 123

def each_dirty_method(&block)
  @dirty_methods.each_key(&block)
end

#fetch_class(name) ⇒ Object

This method does not work in transaction.



407
408
409
410
411
412
# File 'lib/bitclust/methoddatabase.rb', line 407

def fetch_class(name)
  id = intern_classname(name) or
      raise ClassNotFound, "class not found: #{name.inspect}"
  load_class(id) or
      raise "must not happen: #{name.inspect}, #{id.inspect}"
end

#fetch_class_id(id) ⇒ Object



414
415
416
417
# File 'lib/bitclust/methoddatabase.rb', line 414

def fetch_class_id(id)
  load_class(id) or
      raise ClassNotFound, "class not found: #{id.inspect}"
end

#fetch_doc(name) ⇒ Object



330
331
332
333
# File 'lib/bitclust/methoddatabase.rb', line 330

def fetch_doc(name)
  docmap()[libname2id(name)] or
      raise DocNotFound, "doc not found: #{name.inspect}"
end

#fetch_library(name) ⇒ Object



353
354
355
356
# File 'lib/bitclust/methoddatabase.rb', line 353

def fetch_library(name)
  librarymap()[libname2id(name)] or
      raise LibraryNotFound, "library not found: #{name.inspect}"
end

#fetch_library_id(id) ⇒ Object



358
359
360
361
# File 'lib/bitclust/methoddatabase.rb', line 358

def fetch_library_id(id)
  librarymap()[id] or
      raise LibraryNotFound, "library not found: #{id.inspect}"
end

#fetch_method(spec) ⇒ Object



499
500
501
# File 'lib/bitclust/methoddatabase.rb', line 499

def fetch_method(spec)
  fetch_class(spec.klass).fetch_method(spec)
end

#fetch_methods(spec) ⇒ Object



495
496
497
# File 'lib/bitclust/methoddatabase.rb', line 495

def fetch_methods(spec)
  fetch_class(spec.klass).fetch_methods(spec)
end

#get_class(name) ⇒ Object

get a class entry if it exists, or create a new class entry object if it doesn't exist.



396
397
398
399
400
401
402
403
404
# File 'lib/bitclust/methoddatabase.rb', line 396

def get_class(name)
  if id = intern_classname(name)
    load_class(id) or
        raise "must not happen: #{name.inspect}, #{id.inspect}"
  else
    id = classname2id(name)
    @classmap[id] ||= ClassEntry.new(self, id)
  end
end

#get_doc(name) ⇒ Object



325
326
327
328
# File 'lib/bitclust/methoddatabase.rb', line 325

def get_doc(name)
  id = libname2id(name)
  docmap()[id] ||= DocEntry.new(self, id)
end

#get_library(name) ⇒ Object



348
349
350
351
# File 'lib/bitclust/methoddatabase.rb', line 348

def get_library(name)
  id = libname2id(name)
  librarymap()[id] ||= LibraryEntry.new(self, id)
end

#get_method(spec) ⇒ Object



491
492
493
# File 'lib/bitclust/methoddatabase.rb', line 491

def get_method(spec)
  get_class(spec.klass).get_method(spec)
end

#initObject



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/bitclust/methoddatabase.rb', line 50

def init
  prefix = @prefix || raise
  FileUtils.rm_rf prefix
  FileUtils.mkdir_p prefix
  Dir.mkdir "#{prefix}/library"
  Dir.mkdir "#{prefix}/class"
  Dir.mkdir "#{prefix}/method"
  Dir.mkdir "#{prefix}/doc"
  FileUtils.touch "#{prefix}/properties"
  FileUtils.touch "#{prefix}/refs"
end

#librariesObject

Library Entry



339
340
341
# File 'lib/bitclust/methoddatabase.rb', line 339

def libraries
  librarymap().values
end

#make_refsObject



225
226
227
228
229
230
231
232
# File 'lib/bitclust/methoddatabase.rb', line 225

def make_refs
  [classes, libraries, methods, docs].each do |es|
    es.each do |e|
      refs().extract(e)
    end
  end
  refs
end

#methodsObject



487
488
489
# File 'lib/bitclust/methoddatabase.rb', line 487

def methods
  classes().map {|c| c.entries }.flatten
end

#open_class(name) {|c| ... } ⇒ Object

Yields:

  • (c)


427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/bitclust/methoddatabase.rb', line 427

def open_class(name)
  check_transaction
  id = classname2id(name)
  if exist?("class/#{id}")
    c = load_class(id) || raise
    c.clear
  else
    c = (@classmap[id] ||= ClassEntry.new(self, id))
  end
  yield c
  dirty_class c
  c
end

#open_library(name, reopen = false) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
# File 'lib/bitclust/methoddatabase.rb', line 363

def open_library(name, reopen = false)
  check_transaction
  map = librarymap()
  id = libname2id(name)
  if lib = map[id]
    lib.clear unless reopen
  else
    lib = (map[id] ||= LibraryEntry.new(self, id))
  end
  dirty_library lib
  lib
end

#open_method(id) {|m| ... } ⇒ Object

Return existing/newly created MethodEntry from the given MethodID

FIXME: see kind

Yields:

  • (m)


472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/bitclust/methoddatabase.rb', line 472

def open_method(id)
  check_transaction
  if m = id.klass.get_method(id)
    m.clear
  else
    m = MethodEntry.new(self, id.idstring)
    id.klass.add_method m
  end
  m.library = id.library
  m.klass   = id.klass
  yield m
  dirty_method m
  m
end

#search_classes(pattern) ⇒ Object



419
420
421
422
423
424
425
# File 'lib/bitclust/methoddatabase.rb', line 419

def search_classes(pattern)
  cs = _search_classes(pattern)
  if cs.empty?
    raise ClassNotFound, "no such class: #{pattern}"
  end
  cs
end

#search_methods(pattern) ⇒ Object



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
# File 'lib/bitclust/methoddatabase.rb', line 503

def search_methods(pattern)
  result = _search_methods(pattern)
  if result.fail?
    if result.classes.empty?
      loc = pattern.klass ? pattern.klass + '.' : ''
      raise MethodNotFound, "no such method: #{loc}#{pattern.method}"
    end
    if result.classes.size <= 5
      loc = result.classes.map {|c| c.label }.join(', ')
    else
      loc = "#{result.classes.size} classes"
    end
    raise MethodNotFound, "no such method in #{loc}: #{pattern.method}"
  end
  result
end

#update_by_file(path, libname) ⇒ Object



162
163
164
165
# File 'lib/bitclust/methoddatabase.rb', line 162

def update_by_file(path, libname)
  check_transaction
  RRDParser.new(self).parse_file(path, libname, properties())
end

#update_by_markdowntree(md_root) ⇒ Object

Markdown ツリー(manual/api)を直接パースして DB を更新する(M3)。 front matter の library: が所属を表すため、ライブラリごとに lib ファイル → メンバーファイル(reopen/redefine のみのファイルは後置。 dynamic include の対象 module が先に定義されている必要があるため)の 順でパースする。版ゲート(since/until)外のライブラリ/メンバーはスキップ。 エントリの source には md 断片が入り、source_location は md の実パスを指す



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/bitclust/methoddatabase.rb', line 173

def update_by_markdowntree(md_root)
  require 'bitclust/markdown_tree'
  require 'bitclust/mdparser'
  @md_root = md_root
  # 描画層(screen.rb)が MDCompiler を選択するためのマーカー
  propset 'source_format', 'markdown'
  tree = MarkdownTree.scan(md_root)
  version = properties()["version"]
  tree.libraries.sort.each do |libname, lib|
    next unless md_version_covers?(version, lib[:since], lib[:until])
    lib_entry = MDParser.new(self).parse_file(File.join(md_root, lib[:path]), libname, properties())
    lib_location = lib_entry.source_location
    # 多重所属(ゲート付き library リスト)は、この版でゲートが生きている
    # membership を持つライブラリだけがメンバーとして取り込む
    members = tree.entities.select { |_, e|
      e[:memberships].any? { |m|
        m[:library] == libname && md_version_covers?(version, m[:since], m[:until])
      }
    }
    sorted = members.keys.sort_by { |path|
      reopen_only = members[path][:kinds].all? { |kind, _| %w[reopen redefine].include?(kind) }
      [reopen_only ? 1 : 0, path]
    }
    sorted.each do |path|
      entity = members[path]
      next unless md_version_covers?(version, entity[:since], entity[:until])
      MDParser.new(self).parse_file(File.join(md_root, path), libname, properties())
    end
    # parse_file は毎回 library の source_location を上書きするため
    # lib ファイルの値へ戻す
    lib_entry.source_location = lib_location
  end
rescue ParseError => e
  # manual/ には今後もより新しい bitclust を必要とする記法が入りうる。
  # 古い gem でパースに失敗したとき原因へたどり着けるよう案内を添える
  raise ParseError, "#{e.message} (ヒント: マニュアルがより新しい bitclust を必要とする記法を含んでいる可能性があります。bitclust の gem を更新すると解決するかもしれません)", e.backtrace
end

#update_by_stdlibtree(root) ⇒ Object



148
149
150
151
152
153
# File 'lib/bitclust/methoddatabase.rb', line 148

def update_by_stdlibtree(root)
  @root = root
  parse_LIBRARIES("#{root}/LIBRARIES", properties()).each do |libname|
    update_by_file "#{root}/#{libname}.rd", libname
  end
end

#update_requiresObject



133
134
135
136
137
# File 'lib/bitclust/methoddatabase.rb', line 133

def update_requires
  libraries.each{|lib|
    lib.requires = lib.all_requires
  }
end

#update_sublibrariesObject



139
140
141
142
143
144
145
146
# File 'lib/bitclust/methoddatabase.rb', line 139

def update_sublibraries
  libraries.each{|lib|
    re = /\A#{lib.name}\// 
    libraries.each{|l|
      lib.sublibrary(l) if re =~ l.name
    }
  }
end