Module: Jazzy::DocBuilder

Defined in:
lib/jazzy/doc_builder.rb,
lib/jazzy/docset_builder.rb

Overview

This module handles HTML generation, file writing, asset copying, and generally building docs given sourcekitten output

Defined Under Namespace

Classes: DocsetBuilder

Class Method Summary collapse

Class Method Details

.build(options) ⇒ SourceModule

Build documentation from the given options

Parameters:

Returns:



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/jazzy/doc_builder.rb', line 47

def self.build(options)
  if options.sourcekitten_sourcefile
    stdout = options.sourcekitten_sourcefile.read
  else
    if options.podspec_configured
      stdout = PodspecDocumenter.new(options.podspec).sourcekitten_output
    else
      stdout = Dir.chdir(options.source_directory) do
        arguments = SourceKitten.arguments_from_options(options)
        SourceKitten.run_sourcekitten(arguments)
      end
    end
    unless $?.success?
      warn 'Please pass in xcodebuild arguments using -x'
      warn 'If build arguments are correct, please file an issue on ' \
        'https://github.com/realm/jazzy/issues'
      exit $?.exitstatus || 1
    end
  end

  build_docs_for_sourcekitten_output(stdout, options)
end

.build_docs(output_dir, docs, source_module) ⇒ Object

Build & write HTML docs to disk from structured docs array

Parameters:

  • output_dir (String)

    Root directory to write docs

  • docs (Array)

    Array of structured docs

  • options (Config)

    Build options

  • doc_structure (Array)

    @see #doc_structure_for_docs



75
76
77
78
79
80
81
82
83
84
# File 'lib/jazzy/doc_builder.rb', line 75

def self.build_docs(output_dir, docs, source_module)
  each_doc(output_dir, docs) do |doc, path|
    prepare_output_dir(path.parent, false)
    depth = path.relative_path_from(output_dir).each_filename.count - 1
    path_to_root = '../' * depth
    path.open('w') do |file|
      file.write(document(source_module, doc, path_to_root))
    end
  end
end

.build_docs_for_sourcekitten_output(sourcekitten_output, options) ⇒ SourceModule

Build docs given sourcekitten output

Parameters:

  • sourcekitten_output (String)

    Output of sourcekitten command

  • options (Config)

    Build options

Returns:



130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/jazzy/doc_builder.rb', line 130

def self.build_docs_for_sourcekitten_output(sourcekitten_output, options)
  (docs, coverage, undocumented) = SourceKitten.parse(
    sourcekitten_output,
    options.min_acl,
    options.skip_undocumented,
  )

  prepare_output_dir(options.output, options.clean)
  write_lint_report(undocumented, options)

  unless options.skip_documentation
    build_site(docs, coverage, options)
  end
end

.build_site(docs, coverage, options) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/jazzy/doc_builder.rb', line 101

def self.build_site(docs, coverage, options)
  warn 'building site'

  structure = doc_structure_for_docs(docs)

  docs << SourceDeclaration.new.tap do |sd|
    sd.name = 'index'
    sd.children = []
  end

  source_module = SourceModule.new(options, docs, structure, coverage)

  output_dir = options.output
  build_docs(output_dir, source_module.docs, source_module)

  copy_assets(output_dir)

  DocsetBuilder.new(output_dir, source_module).build!

  friendly_path = relative_path_if_inside(output_dir, Pathname.pwd)
  puts "jam out ♪♫ to your fresh new docs in `#{friendly_path}`"

  source_module
end

.copy_assets(destination) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
# File 'lib/jazzy/doc_builder.rb', line 223

def self.copy_assets(destination)
  assets_directory = Config.instance.theme_directory + 'assets'
  FileUtils.cp_r(assets_directory.children, destination)
  Pathname.glob(destination + 'css/**/*.scss').each do |scss|
    contents = scss.read
    css = Sass::Engine.new(contents, syntax: :scss).render
    css_filename = scss.sub(/\.scss$/, '')
    css_filename.open('w') { |f| f.write(css) }
    FileUtils.rm scss
  end
end

.decl_for_token(token) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/jazzy/doc_builder.rb', line 154

def self.decl_for_token(token)
  if token['key.parsed_declaration']
    token['key.parsed_declaration']
  elsif token['key.annotated_decl']
    token['key.annotated_decl'].gsub(/<[^>]+>/, '')
  elsif token['key.name']
    token['key.name']
  else
    'unknown declaration'
  end
end

.doc_structure_for_docs(docs) ⇒ Array

Generate doc structure to be used in sidebar navigation

Returns:

  • (Array)

    doc structure comprised of section names & child names & URLs



30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/jazzy/doc_builder.rb', line 30

def self.doc_structure_for_docs(docs)
  docs.map do |doc|
    children = doc.children
                  .sort_by { |c| [c.nav_order, c.name] }
                  .map do |child|
      { name: child.name, url: child.url }
    end
    {
      section: doc.name,
      children: children,
    }
  end
end

.document(source_module, doc_model, path_to_root) ⇒ Object

Build Mustache document from single parsed doc

Parameters:

  • options (Config)

    Build options

  • doc_model (Hash)

    Parsed doc. @see SourceKitten.parse

  • path_to_root (String)
  • doc_structure (Array)

    doc structure comprised of section names and child names and URLs. @see doc_structure_for_docs



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/jazzy/doc_builder.rb', line 344

def self.document(source_module, doc_model, path_to_root)
  if doc_model.name == 'index'
    return document_index(source_module, path_to_root)
  end

  doc = Doc.new # Mustache model instance
  doc[:custom_head] = Config.instance.custom_head
  doc[:doc_coverage] = source_module.doc_coverage unless
    Config.instance.hide_documentation_coverage
  doc[:name] = doc_model.name
  doc[:kind] = doc_model.type.name
  doc[:dash_type] = doc_model.type.dash_type
  doc[:declaration] = doc_model.declaration
  doc[:overview] = Jazzy.markdown.render(doc_model.overview)
  doc[:structure] = source_module.doc_structure
  doc[:tasks] = render_tasks(source_module, doc_model.children)
  doc[:module_name] = source_module.name
  doc[:author_name] = source_module.author_name
  doc[:github_url] = source_module.github_url
  doc[:dash_url] = source_module.dash_url
  doc[:path_to_root] = path_to_root
  doc.render.gsub(ELIDED_AUTOLINK_TOKEN, path_to_root)
end

.document_index(source_module, path_to_root) ⇒ Object

Build index Mustache document

Parameters:

  • options (Config)

    Build options

  • path_to_root (String)
  • doc_structure (Array)

    doc structure comprised of section names and child names and URLs. @see doc_structure_for_docs



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/jazzy/doc_builder.rb', line 240

def self.document_index(source_module, path_to_root)
  doc = Doc.new # Mustache model instance
  doc[:name] = source_module.name
  doc[:overview] = ReadmeGenerator.generate(source_module)
  doc[:custom_head] = Config.instance.custom_head
  doc[:doc_coverage] = source_module.doc_coverage unless
    Config.instance.hide_documentation_coverage
  doc[:structure] = source_module.doc_structure
  doc[:module_name] = source_module.name
  doc[:author_name] = source_module.author_name
  doc[:github_url] = source_module.github_url
  doc[:dash_url] = source_module.dash_url
  doc[:path_to_root] = path_to_root
  doc[:hide_name] = true
  doc.render
end

.each_doc(output_dir, docs, &block) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/jazzy/doc_builder.rb', line 86

def self.each_doc(output_dir, docs, &block)
  docs.each do |doc|
    next if doc.name != 'index' && doc.children.count == 0
    # Assuming URL is relative to documentation root:
    path = output_dir + (doc.url || "#{doc.name}.html")
    block.call(doc, path)
    next if doc.name == 'index'
    each_doc(
      output_dir,
      doc.children,
      &block
    )
  end
end

.filepath_for_token(token) ⇒ Object



166
167
168
169
170
171
172
# File 'lib/jazzy/doc_builder.rb', line 166

def self.filepath_for_token(token)
  if ENV['JAZZY_INTEGRATION_SPECS']
    Pathname.new(token['key.filepath']).basename.to_s
  else
    token['key.filepath']
  end
end

.gh_token_url(item, source_module) ⇒ Object

Construct Github token URL

Parameters:

  • item (Hash)

    Parsed doc child item

  • options (Config)

    Build options



267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/jazzy/doc_builder.rb', line 267

def self.gh_token_url(item, source_module)
  return unless github_prefix = source_module.github_file_prefix
  return unless should_link_to_github(item.file)
  gh_line = if item.start_line && (item.start_line != item.end_line)
              "#L#{item.start_line}-L#{item.end_line}"
            else
              "#L#{item.line}"
            end
  relative_file_path = item.file.realpath.relative_path_from(
    source_module.root_path)
  "#{github_prefix}/#{relative_file_path}#{gh_line}"
end

.line_number_for_token(token) ⇒ Object



174
175
176
177
178
179
180
# File 'lib/jazzy/doc_builder.rb', line 174

def self.line_number_for_token(token)
  if token['key.doc.line']
    token['key.doc.line'] # Objective-C
  else
    token['key.parsed_scope.start'] # Swift
  end
end

.make_task(mark, uid, items) ⇒ Object



308
309
310
311
312
313
314
315
316
# File 'lib/jazzy/doc_builder.rb', line 308

def self.make_task(mark, uid, items)
  {
    name: mark.name,
    uid: URI.encode(uid),
    items: items,
    pre_separator: mark.has_start_dash,
    post_separator: mark.has_end_dash,
  }
end

.prepare_output_dir(output_dir, clean) ⇒ Object

mkdir -p output directory and clean if option is set



22
23
24
25
# File 'lib/jazzy/doc_builder.rb', line 22

def self.prepare_output_dir(output_dir, clean)
  FileUtils.rm_r output_dir if clean && output_dir.directory?
  FileUtils.mkdir_p output_dir
end

.relative_path_if_inside(path, base_path) ⇒ Object



145
146
147
148
149
150
151
152
# File 'lib/jazzy/doc_builder.rb', line 145

def self.relative_path_if_inside(path, base_path)
  relative = path.relative_path_from(base_path)
  if relative.to_path =~ %r{/^..(\/|$)/}
    path
  else
    relative
  end
end

.render_item(item, source_module) ⇒ Object

Build mustache item for a top-level doc

Parameters:

  • item (Hash)

    Parsed doc child item

  • options (Config)

    Build options



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/jazzy/doc_builder.rb', line 283

def self.render_item(item, source_module)
  # Combine abstract and discussion into abstract
  abstract = (item.abstract || '') + (item.discussion || '')
  item_render = {
    name:                    item.name,
    abstract:                render_markdown(abstract),
    declaration:             item.declaration,
    usr:                     item.usr,
    dash_type:               item.type.dash_type,
    github_token_url:        gh_token_url(item, source_module),
    default_impl_abstract:   render_markdown(item.default_impl_abstract),
    from_protocol_extension: item.from_protocol_extension,
    return:                  render_markdown(item.return),
    parameters:              (item.parameters if item.parameters.any?),
    url:                     (item.url if item.children.any?),
    start_line:              item.start_line,
    end_line:                item.end_line,
  }
  item_render.reject { |_, v| v.nil? }
end

.render_markdown(markdown) ⇒ Object



304
305
306
# File 'lib/jazzy/doc_builder.rb', line 304

def self.render_markdown(markdown)
  Jazzy.markdown.render(markdown) if markdown
end

.render_tasks(source_module, children) ⇒ Object

Render tasks for Mustache document

Parameters:

  • options (Config)

    Build options

  • doc_model (Hash)

    Parsed doc. @see SourceKitten.parse



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/jazzy/doc_builder.rb', line 321

def self.render_tasks(source_module, children)
  marks = children.map(&:mark).uniq
  mark_names_counts = {}
  marks.map do |mark|
    mark_children = children.select { |child| child.mark == mark }
    items = mark_children.map { |child| render_item(child, source_module) }
    uid = (mark.name || 'Unnamed').to_s
    if mark_names_counts.key?(uid)
      mark_names_counts[uid] += 1
      uid += (mark_names_counts[uid]).to_s
    else
      mark_names_counts[uid] = 1
    end
    make_task(mark, uid, items)
  end
end


257
258
259
260
261
262
# File 'lib/jazzy/doc_builder.rb', line 257

def self.should_link_to_github(file)
  return unless file
  file = file.realpath.to_path
  source_directory = Config.instance.source_directory.to_path
  file.start_with?(source_directory)
end

.warnings_for_tokens(tokens_by_file) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/jazzy/doc_builder.rb', line 182

def self.warnings_for_tokens(tokens_by_file)
  warnings = []
  tokens_by_file.each_key do |file|
    tokens_by_file[file].each do |token|
      warnings << {
        file: filepath_for_token(token),
        line: line_number_for_token(token),
        symbol: token['key.name'],
        symbol_kind: token['key.kind'],
        warning: 'undocumented',
      }
    end
  end
  warnings
end

.write_lint_report(undocumented, options) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/jazzy/doc_builder.rb', line 198

def self.write_lint_report(undocumented, options)
  (options.output + 'undocumented.json').open('w') do |f|
    tokens_by_file = undocumented.group_by do |d|
      if d['key.filepath']
        Pathname.new(d['key.filepath']).basename.to_s
      else
        d['key.modulename'] || ''
      end
    end

    warnings = warnings_for_tokens(tokens_by_file)

    lint_report = {
      warnings: warnings,
      source_directory: (
        if ENV['JAZZY_INTEGRATION_SPECS']
          'Specs'
        else
          options.source_directory
        end),
    }
    f.write(lint_report.to_json)
  end
end