1require 'json' 2 3## 4# The JsonIndex generator is designed to complement an HTML generator and 5# produces a JSON search index. This generator is derived from sdoc by 6# Vladimir Kolesnikov and contains verbatim code written by him. 7# 8# This generator is designed to be used with a regular HTML generator: 9# 10# class RDoc::Generator::Darkfish 11# def initialize options 12# # ... 13# @base_dir = Pathname.pwd.expand_path 14# 15# @json_index = RDoc::Generator::JsonIndex.new self, options 16# end 17# 18# def generate 19# # ... 20# @json_index.generate 21# end 22# end 23# 24# == Index Format 25# 26# The index is output as a JSON file assigned to the global variable 27# +search_data+. The structure is: 28# 29# var search_data = { 30# "index": { 31# "searchIndex": 32# ["a", "b", ...], 33# "longSearchIndex": 34# ["a", "a::b", ...], 35# "info": [ 36# ["A", "A", "A.html", "", ""], 37# ["B", "A::B", "A::B.html", "", ""], 38# ... 39# ] 40# } 41# } 42# 43# The same item is described across the +searchIndex+, +longSearchIndex+ and 44# +info+ fields. The +searchIndex+ field contains the item's short name, the 45# +longSearchIndex+ field contains the full_name (when appropriate) and the 46# +info+ field contains the item's name, full_name, path, parameters and a 47# snippet of the item's comment. 48# 49# == LICENSE 50# 51# Copyright (c) 2009 Vladimir Kolesnikov 52# 53# Permission is hereby granted, free of charge, to any person obtaining 54# a copy of this software and associated documentation files (the 55# "Software"), to deal in the Software without restriction, including 56# without limitation the rights to use, copy, modify, merge, publish, 57# distribute, sublicense, and/or sell copies of the Software, and to 58# permit persons to whom the Software is furnished to do so, subject to 59# the following conditions: 60# 61# The above copyright notice and this permission notice shall be 62# included in all copies or substantial portions of the Software. 63# 64# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 65# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 66# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 67# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 68# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 69# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 70# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 71 72class RDoc::Generator::JsonIndex 73 74 include RDoc::Text 75 76 ## 77 # Where the search index lives in the generated output 78 79 SEARCH_INDEX_FILE = File.join 'js', 'search_index.js' 80 81 attr_reader :index # :nodoc: 82 83 ## 84 # Creates a new generator. +parent_generator+ is used to determine the 85 # class_dir and file_dir of links in the output index. 86 # 87 # +options+ are the same options passed to the parent generator. 88 89 def initialize parent_generator, options 90 @parent_generator = parent_generator 91 @store = parent_generator.store 92 @options = options 93 94 @template_dir = File.expand_path '../template/json_index', __FILE__ 95 @base_dir = @parent_generator.base_dir 96 97 @classes = nil 98 @files = nil 99 @index = nil 100 end 101 102 ## 103 # Builds the JSON index as a Hash. 104 105 def build_index 106 reset @store.all_files.sort, @store.all_classes_and_modules.sort 107 108 index_classes 109 index_methods 110 index_pages 111 112 { :index => @index } 113 end 114 115 ## 116 # Output progress information if debugging is enabled 117 118 def debug_msg *msg 119 return unless $DEBUG_RDOC 120 $stderr.puts(*msg) 121 end 122 123 ## 124 # Writes the JSON index to disk 125 126 def generate 127 debug_msg "Generating JSON index" 128 129 debug_msg " writing search index to %s" % SEARCH_INDEX_FILE 130 data = build_index 131 132 return if @options.dry_run 133 134 out_dir = @base_dir + @options.op_dir 135 index_file = out_dir + SEARCH_INDEX_FILE 136 137 FileUtils.mkdir_p index_file.dirname, :verbose => $DEBUG_RDOC 138 139 index_file.open 'w', 0644 do |io| 140 io.set_encoding Encoding::UTF_8 if Object.const_defined? :Encoding 141 io.write 'var search_data = ' 142 143 JSON.dump data, io, 0 144 end 145 146 Dir.chdir @template_dir do 147 Dir['**/*.js'].each do |source| 148 dest = File.join out_dir, source 149 150 FileUtils.install source, dest, :mode => 0644, :verbose => $DEBUG_RDOC 151 end 152 end 153 end 154 155 ## 156 # Adds classes and modules to the index 157 158 def index_classes 159 debug_msg " generating class search index" 160 161 documented = @classes.uniq.select do |klass| 162 klass.document_self_or_methods 163 end 164 165 documented.each do |klass| 166 debug_msg " #{klass.full_name}" 167 record = klass.search_record 168 @index[:searchIndex] << search_string(record.shift) 169 @index[:longSearchIndex] << search_string(record.shift) 170 @index[:info] << record 171 end 172 end 173 174 ## 175 # Adds methods to the index 176 177 def index_methods 178 debug_msg " generating method search index" 179 180 list = @classes.uniq.map do |klass| 181 klass.method_list 182 end.flatten.sort_by do |method| 183 [method.name, method.parent.full_name] 184 end 185 186 list.each do |method| 187 debug_msg " #{method.full_name}" 188 record = method.search_record 189 @index[:searchIndex] << "#{search_string record.shift}()" 190 @index[:longSearchIndex] << "#{search_string record.shift}()" 191 @index[:info] << record 192 end 193 end 194 195 ## 196 # Adds pages to the index 197 198 def index_pages 199 debug_msg " generating pages search index" 200 201 pages = @files.select do |file| 202 file.text? 203 end 204 205 pages.each do |page| 206 debug_msg " #{page.page_name}" 207 record = page.search_record 208 @index[:searchIndex] << search_string(record.shift) 209 @index[:longSearchIndex] << '' 210 record.shift 211 @index[:info] << record 212 end 213 end 214 215 ## 216 # The directory classes are written to 217 218 def class_dir 219 @parent_generator.class_dir 220 end 221 222 ## 223 # The directory files are written to 224 225 def file_dir 226 @parent_generator.file_dir 227 end 228 229 def reset files, classes # :nodoc: 230 @files = files 231 @classes = classes 232 233 @index = { 234 :searchIndex => [], 235 :longSearchIndex => [], 236 :info => [] 237 } 238 end 239 240 ## 241 # Removes whitespace and downcases +string+ 242 243 def search_string string 244 string.downcase.gsub(/\s/, '') 245 end 246 247end 248 249