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