Skip to content

Commit

Permalink
MM: Config model and current-module awareness
Browse files Browse the repository at this point in the history
Nested config file objects with selected attributes for modules.

Add 'doc module name' sourcedeclaration attribute saying where the
declaration was found, vs. 'module name' saying where the decl's
nominal is from.

Quick and dirty updates to prototype code to make it pass rubocop.
  • Loading branch information
johnfairh committed Feb 7, 2024
1 parent 71e69a7 commit f6a7a82
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 150 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ Metrics/ModuleLength:
Metrics/BlockLength:
Enabled: false

Metrics/ParameterLists:
CountKeywordArgs: false

Style/NumericPredicate:
Enabled: false

Expand Down
152 changes: 125 additions & 27 deletions lib/jazzy/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,17 @@ class Config
# rubocop:disable Naming/AccessorMethodName
class Attribute
attr_reader :name, :description, :command_line, :config_file_key,
:default, :parse
:default, :parse, :per_module

def initialize(name, description: nil, command_line: nil,
default: nil, parse: ->(x) { x })
default: nil, parse: ->(x) { x }, per_module: false)
@name = name.to_s
@description = Array(description)
@command_line = Array(command_line)
@default = default
@parse = parse
@config_file_key = full_command_line_name || @name
@per_module = per_module
end

def get(config)
Expand Down Expand Up @@ -51,7 +52,6 @@ def configured?(config)
end

def attach_to_option_parser(config, opt)

return if command_line.empty?

opt.on(*command_line, *description) do |val|
Expand Down Expand Up @@ -135,22 +135,26 @@ def hide_objc?
config_attr :objc_mode,
command_line: '--[no-]objc',
description: 'Generate docs for Objective-C.',
default: false
default: false,
per_module: true

config_attr :umbrella_header,
command_line: '--umbrella-header PATH',
description: 'Umbrella header for your Objective-C framework.',
parse: ->(uh) { expand_path(uh) }
parse: ->(uh) { expand_path(uh) },
per_module: true

config_attr :framework_root,
command_line: '--framework-root PATH',
description: 'The root path to your Objective-C framework.',
parse: ->(fr) { expand_path(fr) }
parse: ->(fr) { expand_path(fr) },
per_module: true

config_attr :sdk,
command_line: '--sdk [iphone|watch|appletv][os|simulator]|macosx',
description: 'The SDK for which your code should be built.',
default: 'macosx'
default: 'macosx',
per_module: true

config_attr :hide_declarations,
command_line: '--hide-declarations [objc|swift] ',
Expand All @@ -176,12 +180,14 @@ def hide_objc?
command_line: ['-b', '--build-tool-arguments arg1,arg2,…argN', Array],
description: 'Arguments to forward to xcodebuild, swift build, or ' \
'sourcekitten.',
default: []
default: [],
per_module: true

config_attr :modules,
description: 'Array of modules that are going to be documented ' \
'It will contain arguments: - name, build-tool-arguments arg1,arg2,…argN, source_directory.',
default: false
command_line: ['--modules Mod1,Mod2,…ModN', Array],
description: 'List of modules to document. Use the config file to set per-module ' \
'build flags, see xxxREADMExxx.',
default: []

alias_config_attr :xcodebuild_arguments, :build_tool_arguments,
command_line: ['-x', '--xcodebuild-arguments arg1,arg2,…argN', Array],
Expand All @@ -191,19 +197,23 @@ def hide_objc?
command_line: ['-s', '--sourcekitten-sourcefile filepath1,…filepathN',
Array],
description: 'File(s) generated from sourcekitten output to parse',
parse: ->(paths) { [paths].flatten.map { |path| expand_path(path) } }
parse: ->(paths) { [paths].flatten.map { |path| expand_path(path) } },
default: [],
per_module: true

config_attr :source_directory,
command_line: '--source-directory DIRPATH',
description: 'The directory that contains the source to be documented',
default: Pathname.pwd,
parse: ->(sd) { expand_path(sd) }
parse: ->(sd) { expand_path(sd) },
per_module: true

config_attr :symbolgraph_directory,
command_line: '--symbolgraph-directory DIRPATH',
description: 'A directory containing a set of Swift Symbolgraph files ' \
'representing the module to be documented',
parse: ->(sd) { expand_path(sd) }
parse: ->(sd) { expand_path(sd) },
per_module: true

config_attr :excluded_files,
command_line: ['-e', '--exclude filepath1,filepath2,…filepathN', Array],
Expand Down Expand Up @@ -267,7 +277,8 @@ def hide_objc?
config_attr :module_name,
command_line: ['-m', '--module MODULE_NAME'],
description: 'Name of module being documented. (e.g. RealmSwift)',
default: ''
default: '',
per_module: true

config_attr :version,
command_line: '--module-version VERSION',
Expand Down Expand Up @@ -516,10 +527,12 @@ def self.parse!
if config.root_url
config.dash_url ||= URI.join(
config.root_url,
"docsets/#{config.module_name}.xml",
"docsets/#{config.module_name}.xml", # XXX help
)
end


config.set_module_configs

config.validate

config
Expand Down Expand Up @@ -575,12 +588,13 @@ def parse_config_file
puts "Using config file #{config_path}"
config_file = read_config_file(config_path)

attrs_by_conf_key, attrs_by_name = %i[config_file_key name].map do |prop|
self.class.all_config_attrs.group_by(&prop)
end
attrs_by_conf_key, attrs_by_name = grouped_attributes

parse_config_hash(config_file, attrs_by_conf_key, attrs_by_name)
end

config_file.each do |key, value|
def parse_config_hash(hash, attrs_by_conf_key, attrs_by_name, override: false)
hash.each do |key, value|
unless attr = attrs_by_conf_key[key]
message = "Unknown config file attribute #{key.inspect}"
if matching_name = attrs_by_name[key]
Expand All @@ -590,10 +604,19 @@ def parse_config_file
warning message
next
end
attr.first.set_if_unconfigured(self, value)
setter = override ? :set : :set_if_unconfigured
attr.first.method(setter).call(self, value)
end
end

self.base_path = nil
# Find keyed versions of the attributes, by config file key and then name-in-code
# Optional block allows filtering/overriding of attribute list.
def grouped_attributes
attrs = self.class.all_config_attrs
attrs = yield attrs if block_given?
%i[config_file_key name].map do |property|
attrs.group_by(&property)
end
end

def validate
Expand All @@ -604,20 +627,95 @@ def validate
'`source_host_url` or `source_host_files_url`.'
end

if modules_configured && module_name_configured
raise 'Options `modules` and `module` are both set. See ' \
'XXX readme URL XXX.'
end

if modules_configured && podspec_configured
raise 'Options `modules` and `podspec` are both set. See ' \
'XXX readme URL XXX.'
end

module_configs.each(&:validate_module)
end

def validate_module
if objc_mode &&
build_tool_arguments_configured &&
(framework_root_configured || umbrella_header_configured)
warning 'Option `build_tool_arguments` is set: values passed to ' \
'`framework_root` or `umbrella_header` may be ignored.'
end
end

if modules_configured && module_name_configured
raise 'Jazzy only allows the use of a single command for generating documentation.' \
'Using both module configuration and modules configuration together is not supported.'
# rubocop:enable Metrics/MethodLength

# Module Configs
#
# The user can enter module information in three different ways. This
# consolidates them into one view for the rest of the code.
#
# 1) Single module, back-compatible
# --module Foo etc etc (or not given at all)
#
# 2) Multiple modules, simple, sharing build params
# --modules Foo,Bar,Baz --source-directory Xyz
#
# 3) Multiple modules, custom, different build params but
# inheriting others from the top level.
# This is config-file only.
# - modules
# - module: Foo
# source_directory: Xyz
# build_tool_arguments: [a, b, c]
#
# After this we're left with `config.module_configs` that is an
# array of `Config` objects.

attr_reader :module_configs
attr_reader :module_names

def set_module_configs
@module_configs = parse_module_configs
@module_names = module_configs.map(&:module_name)
@module_names_set = Set.new(module_names)
end

def module_name?(name)
@module_names_set.include?(name)
end

def parse_module_configs
return [self] unless modules_configured

raise 'Config file key `modules` must be an array' unless modules.is_a?(Array)

if modules.first.is_a?(String)
# Massage format (2) into (3)
self.modules = modules.map { { 'module' => _1 } }
end

# Allow per-module overrides of only some config options
attrs_by_conf_key, attrs_by_name =
grouped_attributes { _1.select(&:per_module) }

modules.map do |module_hash|
mod_name = module_hash['module'] || ''
raise 'Missing `modules.module` config key' if mod_name.empty?

dup.tap do |module_config|
module_config.parse_config_hash(
module_hash, attrs_by_conf_key, attrs_by_name, override: true
)
end
end
end

# rubocop:enable Metrics/MethodLength
# For podspec query
def module_name_known?
module_name_configured || modules_configured
end

def locate_config_file
return config_file if config_file
Expand Down
4 changes: 2 additions & 2 deletions lib/jazzy/doc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ def docs_title
elsif config.version_configured
# Fake version for integration tests
version = ENV['JAZZY_FAKE_MODULE_VERSION'] || config.version
"#{config.module_name} #{version} Docs"
"#{config.module_configs.first.module_name} #{version} Docs" # XXX help
else
"#{config.module_name} Docs"
"#{config.module_configs.first.module_name} Docs" # XXX
end
end

Expand Down
52 changes: 18 additions & 34 deletions lib/jazzy/doc_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,40 +67,25 @@ def self.children_for_doc(doc)

# Build documentation from the given options
# @param [Config] options
# @return [SourceModule] the documented source module
def self.build(options)
if options.modules_configured
stdout = multiple_modules(options)
elsif options.sourcekitten_sourcefile_configured
stdout = "[#{options.sourcekitten_sourcefile.map(&:read).join(',')}]"
elsif options.podspec_configured
pod_documenter = PodspecDocumenter.new(options.podspec)
stdout = pod_documenter.sourcekitten_output(options)
elsif options.swift_build_tool == :symbolgraph
stdout = SymbolGraph.build(options)
else
stdout = Dir.chdir(options.source_directory) do
arguments = SourceKitten.arguments_from_options(options)
SourceKitten.run_sourcekitten(arguments)
module_jsons = options.module_configs.map do |module_config|
if module_config.podspec_configured
# Config#validate guarantees not multi-module here
pod_documenter = PodspecDocumenter.new(options.podspec)
pod_documenter.sourcekitten_output(options)
elsif !module_config.sourcekitten_sourcefile.empty?
"[#{module_config.sourcekitten_sourcefile.map(&:read).join(',')}]"
elsif module_config.swift_build_tool == :symbolgraph
SymbolGraph.build(module_config)
else
Dir.chdir(module_config.source_directory) do
arguments = SourceKitten.arguments_from_options(module_config)
SourceKitten.run_sourcekitten(arguments)
end
end
end

build_docs_for_sourcekitten_output(stdout, options)
end

# Build Xcode project for multiple modules and parse the api documentation into a string
# @param [Config] options
# @return String the documented source module
def self.multiple_modules(options)
modules_parsed = Array[]
options.modules.each do |arguments|
module_parsed_string = Dir.chdir(arguments['source_directory']) do
arguments = SourceKitten.arguments_from_options(options) + (arguments['build_tool_arguments']||[])
SourceKitten.run_sourcekitten(arguments)
end
modules_parsed.push(module_parsed_string)
end
stdout = "[#{modules_parsed.join(',')}]"
build_docs_for_sourcekitten_output(module_jsons, options)
end

# Build & write HTML docs to disk from structured docs array
Expand Down Expand Up @@ -165,16 +150,15 @@ def self.build_site(docs, coverage, options)
end

# Build docs given sourcekitten output
# @param [String] sourcekitten_output Output of sourcekitten command
# @param [Array<String>] sourcekitten_output Output of sourcekitten command for each module
# @param [Config] options Build options
# @return [SourceModule] the documented source module
def self.build_docs_for_sourcekitten_output(sourcekitten_output, options)
(docs, stats) = SourceKitten.parse(
sourcekitten_output,
options,
DocumentationGenerator.source_docs,
)

prepare_output_dir(options.output, options.clean)

stats.report
Expand Down Expand Up @@ -464,4 +448,4 @@ def self.document(source_module, doc_model, path_to_root)
end
# rubocop:enable Metrics/MethodLength
end
end
end
2 changes: 1 addition & 1 deletion lib/jazzy/podspec_documenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def self.apply_config_defaults(podspec, config)
config.author_name = author_name(podspec)
config.author_name_configured = true
end
unless config.module_name_configured
unless config.module_name_known?
config.module_name = podspec.module_name
config.module_name_configured = true
end
Expand Down
Loading

0 comments on commit f6a7a82

Please sign in to comment.