PageRenderTime 25ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/heroku/ruby/1.9.1/gems/rdoc-3.7/lib/rdoc/ri/store.rb

https://github.com/sebasdiaz/alquiler
Ruby | 362 lines | 185 code | 89 blank | 88 comment | 8 complexity | a5726ebf52f7e97f9b420cf58096e317 MD5 | raw file
  1. require 'rdoc/code_objects'
  2. require 'fileutils'
  3. ##
  4. # A set of ri data.
  5. #
  6. # The store manages reading and writing ri data for a project (gem, path,
  7. # etc.) and maintains a cache of methods, classes and ancestors in the
  8. # store.
  9. #
  10. # The store maintains a #cache of its contents for faster lookup. After
  11. # adding items to the store it must be flushed using #save_cache. The cache
  12. # contains the following structures:
  13. #
  14. # @cache = {
  15. # :class_methods => {}, # class name => class methods
  16. # :instance_methods => {}, # class name => instance methods
  17. # :attributes => {}, # class name => attributes
  18. # :modules => [], # classes and modules in this store
  19. # :ancestors => {}, # class name => ancestor names
  20. # }
  21. #--
  22. # TODO need to store the list of files and prune classes
  23. class RDoc::RI::Store
  24. ##
  25. # If true this Store will not write any files
  26. attr_accessor :dry_run
  27. ##
  28. # Path this store reads or writes
  29. attr_accessor :path
  30. ##
  31. # Type of ri datastore this was loaded from. See RDoc::RI::Driver,
  32. # RDoc::RI::Paths.
  33. attr_accessor :type
  34. ##
  35. # The contents of the Store
  36. attr_reader :cache
  37. ##
  38. # The encoding of the contents in the Store
  39. attr_accessor :encoding
  40. ##
  41. # Creates a new Store of +type+ that will load or save to +path+
  42. def initialize path, type = nil
  43. @dry_run = false
  44. @type = type
  45. @path = path
  46. @encoding = nil
  47. @cache = {
  48. :ancestors => {},
  49. :attributes => {},
  50. :class_methods => {},
  51. :encoding => @encoding,
  52. :instance_methods => {},
  53. :modules => [],
  54. }
  55. end
  56. ##
  57. # Ancestors cache accessor. Maps a klass name to an Array of its ancestors
  58. # in this store. If Foo in this store inherits from Object, Kernel won't be
  59. # listed (it will be included from ruby's ri store).
  60. def ancestors
  61. @cache[:ancestors]
  62. end
  63. ##
  64. # Attributes cache accessor. Maps a class to an Array of its attributes.
  65. def attributes
  66. @cache[:attributes]
  67. end
  68. ##
  69. # Path to the cache file
  70. def cache_path
  71. File.join @path, 'cache.ri'
  72. end
  73. ##
  74. # Path to the ri data for +klass_name+
  75. def class_file klass_name
  76. name = klass_name.split('::').last
  77. File.join class_path(klass_name), "cdesc-#{name}.ri"
  78. end
  79. ##
  80. # Class methods cache accessor. Maps a class to an Array of its class
  81. # methods (not full name).
  82. def class_methods
  83. @cache[:class_methods]
  84. end
  85. ##
  86. # Path where data for +klass_name+ will be stored (methods or class data)
  87. def class_path klass_name
  88. File.join @path, *klass_name.split('::')
  89. end
  90. ##
  91. # Removes empty items and ensures item in each collection are unique and
  92. # sorted
  93. def clean_cache_collection collection # :nodoc:
  94. collection.each do |name, item|
  95. if item.empty? then
  96. collection.delete name
  97. else
  98. # HACK mongrel-1.1.5 documents its files twice
  99. item.uniq!
  100. item.sort!
  101. end
  102. end
  103. end
  104. ##
  105. # Friendly rendition of #path
  106. def friendly_path
  107. case type
  108. when :gem then
  109. sep = Regexp.union(*['/', File::ALT_SEPARATOR].compact)
  110. @path =~ /#{sep}doc#{sep}(.*?)#{sep}ri$/
  111. "gem #{$1}"
  112. when :home then '~/.ri'
  113. when :site then 'ruby site'
  114. when :system then 'ruby core'
  115. else @path
  116. end
  117. end
  118. def inspect # :nodoc:
  119. "#<%s:0x%x %s %p>" % [self.class, object_id, @path, modules.sort]
  120. end
  121. ##
  122. # Instance methods cache accessor. Maps a class to an Array of its
  123. # instance methods (not full name).
  124. def instance_methods
  125. @cache[:instance_methods]
  126. end
  127. ##
  128. # Loads cache file for this store
  129. def load_cache
  130. #orig_enc = @encoding
  131. open cache_path, 'rb' do |io|
  132. @cache = Marshal.load io.read
  133. end
  134. load_enc = @cache[:encoding]
  135. # TODO this feature will be time-consuming to add:
  136. # a) Encodings may be incompatible but transcodeable
  137. # b) Need to warn in the appropriate spots, wherever they may be
  138. # c) Need to handle cross-cache differences in encodings
  139. # d) Need to warn when generating into a cache with diffent encodings
  140. #
  141. #if orig_enc and load_enc != orig_enc then
  142. # warn "Cached encoding #{load_enc} is incompatible with #{orig_enc}\n" \
  143. # "from #{path}/cache.ri" unless
  144. # Encoding.compatible? orig_enc, load_enc
  145. #end
  146. @encoding = load_enc unless @encoding
  147. @cache
  148. rescue Errno::ENOENT
  149. end
  150. ##
  151. # Loads ri data for +klass_name+
  152. def load_class klass_name
  153. open class_file(klass_name), 'rb' do |io|
  154. Marshal.load io.read
  155. end
  156. end
  157. ##
  158. # Loads ri data for +method_name+ in +klass_name+
  159. def load_method klass_name, method_name
  160. open method_file(klass_name, method_name), 'rb' do |io|
  161. Marshal.load io.read
  162. end
  163. end
  164. ##
  165. # Path to the ri data for +method_name+ in +klass_name+
  166. def method_file klass_name, method_name
  167. method_name = method_name.split('::').last
  168. method_name =~ /#(.*)/
  169. method_type = $1 ? 'i' : 'c'
  170. method_name = $1 if $1
  171. method_name = if ''.respond_to? :ord then
  172. method_name.gsub(/\W/) { "%%%02x" % $&[0].ord }
  173. else
  174. method_name.gsub(/\W/) { "%%%02x" % $&[0] }
  175. end
  176. File.join class_path(klass_name), "#{method_name}-#{method_type}.ri"
  177. end
  178. ##
  179. # Modules cache accessor. An Array of all the modules (and classes) in the
  180. # store.
  181. def modules
  182. @cache[:modules]
  183. end
  184. ##
  185. # Writes the cache file for this store
  186. def save_cache
  187. clean_cache_collection @cache[:ancestors]
  188. clean_cache_collection @cache[:attributes]
  189. clean_cache_collection @cache[:class_methods]
  190. clean_cache_collection @cache[:instance_methods]
  191. @cache[:modules].uniq!
  192. @cache[:modules].sort!
  193. @cache[:encoding] = @encoding # this gets set twice due to assert_cache
  194. return if @dry_run
  195. marshal = Marshal.dump @cache
  196. open cache_path, 'wb' do |io|
  197. io.write marshal
  198. end
  199. end
  200. ##
  201. # Writes the ri data for +klass+
  202. def save_class klass
  203. full_name = klass.full_name
  204. FileUtils.mkdir_p class_path(full_name) unless @dry_run
  205. @cache[:modules] << full_name
  206. path = class_file full_name
  207. begin
  208. disk_klass = nil
  209. open path, 'rb' do |io|
  210. disk_klass = Marshal.load io.read
  211. end
  212. klass = disk_klass.merge klass
  213. rescue Errno::ENOENT
  214. end
  215. # BasicObject has no ancestors
  216. ancestors = klass.ancestors.compact.map do |ancestor|
  217. # HACK for classes we don't know about (class X < RuntimeError)
  218. String === ancestor ? ancestor : ancestor.full_name
  219. end
  220. @cache[:ancestors][full_name] ||= []
  221. @cache[:ancestors][full_name].push(*ancestors)
  222. attributes = klass.attributes.map do |attribute|
  223. "#{attribute.definition} #{attribute.name}"
  224. end
  225. unless attributes.empty? then
  226. @cache[:attributes][full_name] ||= []
  227. @cache[:attributes][full_name].push(*attributes)
  228. end
  229. to_delete = []
  230. unless klass.method_list.empty? then
  231. @cache[:class_methods][full_name] ||= []
  232. @cache[:instance_methods][full_name] ||= []
  233. class_methods, instance_methods =
  234. klass.method_list.partition { |meth| meth.singleton }
  235. class_methods = class_methods. map { |method| method.name }
  236. instance_methods = instance_methods.map { |method| method.name }
  237. old = @cache[:class_methods][full_name] - class_methods
  238. to_delete.concat old.map { |method|
  239. method_file full_name, "#{full_name}::#{method}"
  240. }
  241. old = @cache[:instance_methods][full_name] - instance_methods
  242. to_delete.concat old.map { |method|
  243. method_file full_name, "#{full_name}##{method}"
  244. }
  245. @cache[:class_methods][full_name] = class_methods
  246. @cache[:instance_methods][full_name] = instance_methods
  247. end
  248. return if @dry_run
  249. FileUtils.rm_f to_delete
  250. marshal = Marshal.dump klass
  251. open path, 'wb' do |io|
  252. io.write marshal
  253. end
  254. end
  255. ##
  256. # Writes the ri data for +method+ on +klass+
  257. def save_method klass, method
  258. full_name = klass.full_name
  259. FileUtils.mkdir_p class_path(full_name) unless @dry_run
  260. cache = if method.singleton then
  261. @cache[:class_methods]
  262. else
  263. @cache[:instance_methods]
  264. end
  265. cache[full_name] ||= []
  266. cache[full_name] << method.name
  267. return if @dry_run
  268. marshal = Marshal.dump method
  269. open method_file(full_name, method.full_name), 'wb' do |io|
  270. io.write marshal
  271. end
  272. end
  273. end