lib/jazzy/config.rb in jazzy-0.14.4 vs lib/jazzy/config.rb in jazzy-0.15.0

- old
+ new

@@ -11,20 +11,21 @@ # rubocop:disable Metrics/ClassLength 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) config.method(name).call end @@ -132,26 +133,30 @@ default: false 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] ', description: 'Hide declarations in the specified language. Given that ' \ 'generating Swift docs only generates Swift declarations, ' \ @@ -173,33 +178,44 @@ config_attr :build_tool_arguments, command_line: ['-b', '--build-tool-arguments arg1,arg2,…argN', Array], description: 'Arguments to forward to xcodebuild, swift build, or ' \ 'sourcekitten.', + default: [], + per_module: true + + config_attr :modules, + command_line: ['--modules Mod1,Mod2,…ModN', Array], + description: 'List of modules to document. Use the config file to set per-module ' \ + "build flags, see 'Documenting multiple modules' in the README.", default: [] alias_config_attr :xcodebuild_arguments, :build_tool_arguments, command_line: ['-x', '--xcodebuild-arguments arg1,arg2,…argN', Array], description: 'Back-compatibility alias for build_tool_arguments.' config_attr :sourcekitten_sourcefile, 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], description: 'Source file pathnames to be excluded from documentation. ' \ 'Supports wildcards.', @@ -259,11 +275,12 @@ parse: ->(u) { URI(u) } 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', description: 'Version string to use as part of the default docs ' \ 'title and inside the docset.', @@ -282,10 +299,14 @@ config_attr :readme_path, command_line: '--readme FILEPATH', description: 'The path to a markdown README file', parse: ->(rp) { expand_path(rp) } + config_attr :readme_title, + command_line: '--readme-title TITLE', + description: 'The title for the README in the generated documentation' + config_attr :documentation_glob, command_line: '--documentation GLOB', description: 'Glob that matches available documentation', parse: ->(dg) { Pathname.glob(dg) } @@ -315,10 +336,17 @@ config_attr :docset_path, command_line: '--docset-path DIRPATH', description: 'The relative path for the generated docset' + config_attr :docset_title, + command_line: '--docset-title TITLE', + description: 'The title of the generated docset. A simplified version ' \ + 'is used for the filenames associated with the docset. If the ' \ + 'option is not set then the name of the module being documented is ' \ + 'used as the docset title.' + # ──────── URLs ──────── config_attr :root_url, command_line: ['-r', '--root-url URL'], description: 'Absolute URL root where these docs will be stored', @@ -486,10 +514,27 @@ command_line: '--[no-]include-spi-declarations', description: 'Include Swift declarations marked `@_spi` even if ' \ '--min-acl is set to `public` or `open`.', default: false + MERGE_MODULES = %w[all extensions none].freeze + + config_attr :merge_modules, + command_line: "--merge-modules #{MERGE_MODULES.join(' | ')}", + description: 'Control how to display declarations from multiple ' \ + 'modules. `all`, the default, places all declarations of the ' \ + "same kind together. `none` keeps each module's declarations " \ + 'separate. `extensions` is like `none` but merges ' \ + 'cross-module extensions into their extended type.', + default: 'all', + parse: ->(merge) do + return merge.to_sym if MERGE_MODULES.include?(merge) + + raise "Unsupported merge_modules #{merge}, " \ + "supported values: #{MERGE_MODULES.join(', ')}" + end + # rubocop:enable Layout/ArgumentAlignment def initialize self.class.all_config_attrs.each do |attr| attr.set_to_default(self) @@ -505,16 +550,11 @@ config = new config.parse_command_line config.parse_config_file PodspecDocumenter.apply_config_defaults(config.podspec, config) - if config.root_url - config.dash_url ||= URI.join( - config.root_url, - "docsets/#{config.module_name}.xml", - ) - end + config.set_module_configs config.validate config end @@ -567,56 +607,148 @@ self.base_path = config_path.parent 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 - config_file.each do |key, value| + parse_config_hash(config_file, attrs_by_conf_key, attrs_by_name) + end + + 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] message += " (Did you mean #{matching_name.first.config_file_key.inspect}?)" end 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 if source_host_configured && source_host_url.nil? && source_host_files_url.nil? warning 'Option `source_host` is set but has no effect without either ' \ '`source_host_url` or `source_host_files_url`.' end + if modules_configured && module_name_configured + raise 'Options `modules` and `module` are both set which is not supported. ' \ + 'To document multiple modules, use just `modules`.' + end + + if modules_configured && podspec_configured + raise 'Options `modules` and `podspec` are both set which is not supported.' + 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 # 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 multiple_modules? + @module_names.count > 1 + 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 + + # For podspec query + def module_name_known? + module_name_configured || modules_configured + end + def locate_config_file return config_file if config_file source_directory.ascend do |dir| candidate = dir.join('.jazzy.yaml') return candidate if candidate.exist? end - nil end def read_config_file(file) case File.extname(file)