From 69e0cd32fe0d6b4b63506ba98e1f2b3a1467c6fb Mon Sep 17 00:00:00 2001 From: John Fairhurst Date: Fri, 9 Feb 2024 11:29:25 +0000 Subject: [PATCH] MM: docset title independent of module name --- CHANGELOG.md | 4 ++ README.md | 19 +++++++ lib/jazzy/config.rb | 14 ++--- lib/jazzy/doc_builder.rb | 21 ++++---- lib/jazzy/docset_builder.rb | 57 +++++++++++++++----- lib/jazzy/docset_builder/info_plist.mustache | 2 +- lib/jazzy/source_module.rb | 21 ++++---- spec/integration_specs | 2 +- 8 files changed, 98 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d41b6d65f..70e9b22f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ ##### Enhancements +* Add `--docset-title` to set the title of a docset separately + to the module name. + [John Fairhurst](https://github.com/johnfairh) + * Support Swift 5.9 symbolgraph extension symbols. [John Fairhurst](https://github.com/johnfairh) [#1368](https://github.com/realm/jazzy/issues/1368) diff --git a/README.md b/README.md index 72880a414..07501391d 100644 --- a/README.md +++ b/README.md @@ -423,6 +423,24 @@ jazzy --swift-version 5.7 DEVELOPER_DIR=/Applications/Xcode_14.app/Contents/Developer jazzy ``` +### Dash Docset Support + +As well as the browsable HTML documentation, Jazzy creates a _docset_ for use +with the [Dash][dash] app. + +By default the docset is created at `docs/docsets/ModuleName.tgz`. Use +`--docset-path` to create it somewhere else; use `--docset-title` to change +the docset's title. + +Use `--docset-playground-url` and `--docset-icon` to further customize the +docset. + +If you set both `--root-url` to be the (https://) URL where you plan to deploy +your documentation and `--version` to give your documentation a version number +then Jazzy also creates a docset feed XML file and includes an "Install in Dash" +button on the site. This lets users who are browsing your documentation on the +web install and start using the docs in Dash locally. + ## Linux Jazzy uses [SourceKitten][sourcekitten] to communicate with the Swift build @@ -576,3 +594,4 @@ read [our blog](https://realm.io/news) or say hi on twitter [bundler]: https://rubygems.org/gems/bundler [mustache]: https://mustache.github.io "Mustache" [spm]: https://swift.org/package-manager/ "Swift Package Manager" +[dash]: https://kapeli.com/dash/ "Dash" diff --git a/lib/jazzy/config.rb b/lib/jazzy/config.rb index 324bf6233..9e6139850 100644 --- a/lib/jazzy/config.rb +++ b/lib/jazzy/config.rb @@ -334,6 +334,13 @@ def hide_objc? 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, @@ -524,13 +531,6 @@ def self.parse! 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", # XXX help - ) - end - config.set_module_configs config.validate diff --git a/lib/jazzy/doc_builder.rb b/lib/jazzy/doc_builder.rb index e396c4531..7c07e2ab0 100644 --- a/lib/jazzy/doc_builder.rb +++ b/lib/jazzy/doc_builder.rb @@ -90,11 +90,9 @@ def self.build(options) # Build & write HTML docs to disk from structured docs array # @param [String] output_dir Root directory to write docs - # @param [Array] docs Array of structured docs - # @param [Config] options Build options - # @param [Array] doc_structure @see #doc_structure_for_docs - def self.build_docs(output_dir, docs, source_module) - each_doc(output_dir, docs) do |doc, path| + # @param [SourceModule] source_module All info to generate docs + def self.build_docs(output_dir, source_module) + each_doc(output_dir, source_module.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 @@ -126,10 +124,13 @@ def self.build_site(docs, coverage, options) docs << SourceDocument.make_index(options.readme_path) - source_module = SourceModule.new(options, docs, structure, coverage) - output_dir = options.output - build_docs(output_dir, source_module.docs, source_module) + + docset_builder = DocsetBuilder.new(output_dir) + + source_module = SourceModule.new(docs, structure, coverage, docset_builder) + + build_docs(output_dir, source_module) unless options.disable_search warn 'building search index' @@ -139,7 +140,7 @@ def self.build_site(docs, coverage, options) copy_extensions(source_module, output_dir) copy_theme_assets(output_dir) - DocsetBuilder.new(output_dir, source_module).build! + docset_builder.build!(source_module.all_declarations) generate_badge(source_module.doc_coverage, options) @@ -259,7 +260,7 @@ def self.new_document(source_module, doc_model) doc[:github_url] = doc[:source_host_url] doc[:github_token_url] = doc[:source_host_item_url] end - doc[:dash_url] = source_module.dash_url + doc[:dash_url] = source_module.dash_feed_url end end diff --git a/lib/jazzy/docset_builder.rb b/lib/jazzy/docset_builder.rb index 6c7be9abe..7970e4505 100644 --- a/lib/jazzy/docset_builder.rb +++ b/lib/jazzy/docset_builder.rb @@ -14,37 +14,43 @@ class DocsetBuilder attr_reader :source_module attr_reader :docset_dir attr_reader :documents_dir + attr_reader :name - def initialize(generated_docs_dir, source_module) - @source_module = source_module + def initialize(generated_docs_dir) + @name = config.docset_title || config.module_names.first docset_path = config.docset_path || - "docsets/#{source_module.name}.docset" + "docsets/#{safe_name}.docset" @docset_dir = generated_docs_dir + docset_path @generated_docs_dir = generated_docs_dir @output_dir = docset_dir.parent @documents_dir = docset_dir + 'Contents/Resources/Documents/' end - def build! + def build!(all_declarations) docset_dir.rmtree if docset_dir.exist? copy_docs copy_icon if config.docset_icon write_plist - create_index + create_index(all_declarations) create_archive create_xml if config.version && config.root_url end private + def safe_name + name.gsub(/[^a-z0-9_\-]+/i, '_') + end + def write_plist info_plist_path = docset_dir + 'Contents/Info.plist' info_plist_path.open('w') do |plist| template = Pathname(__dir__) + 'docset_builder/info_plist.mustache' plist << Mustache.render( template.read, - lowercase_name: source_module.name.downcase, - name: source_module.name, + lowercase_name: name.downcase, + lowercase_safe_name: safe_name.downcase, + name: name, root_url: config.root_url, playground_url: config.docset_playground_url, ) @@ -52,7 +58,7 @@ def write_plist end def create_archive - target = "#{source_module.name}.tgz" + target = "#{safe_name}.tgz" source = docset_dir.basename.to_s options = { chdir: output_dir.to_s, @@ -70,17 +76,17 @@ def copy_docs end def copy_icon - FileUtils.cp config.docset_icon, @docset_dir + 'icon.png' + FileUtils.cp config.docset_icon, docset_dir + 'icon.png' end - def create_index + def create_index(all_declarations) search_index_path = docset_dir + 'Contents/Resources/docSet.dsidx' SQLite3::Database.new(search_index_path.to_s) do |db| db.execute('CREATE TABLE searchIndex(' \ 'id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT);') db.execute('CREATE UNIQUE INDEX anchor ON ' \ 'searchIndex (name, type, path);') - source_module.all_declarations.select(&:type).each do |doc| + all_declarations.select(&:type).each do |doc| db.execute('INSERT OR IGNORE INTO searchIndex(name, type, path) ' \ 'VALUES (?, ?, ?);', [doc.name, doc.type.dash_type, doc.filepath]) end @@ -88,12 +94,37 @@ def create_index end def create_xml - (output_dir + "#{source_module.name}.xml").open('w') do |xml| - url = URI.join(config.root_url, "docsets/#{source_module.name}.tgz") + (output_dir + "#{safe_name}.xml").open('w') do |xml| + url = URI.join(config.root_url, "docsets/#{safe_name}.tgz") xml << "#{config.version}#{url}" \ "\n" end end + + # The web URL where the user intends to place the docset XML file. + def dash_url + return nil unless config.dash_url || config.root_url + + config.dash_url || + URI.join( + config.root_url, + "docsets/#{safe_name}.xml", + ) + end + + public + + # The dash-feed:// URL that links from the Dash icon in generated + # docs. This is passed to the Dash app and encodes the actual web + # `dash_url` where the user has placed the XML file. + # + # Unfortunately for historical reasons this is *also* called the + # 'dash_url' where it appears in mustache templates and so on. + def dash_feed_url + dash_url&.then do |url| + "dash-feed://#{ERB::Util.url_encode(url.to_s)}" + end + end end end end diff --git a/lib/jazzy/docset_builder/info_plist.mustache b/lib/jazzy/docset_builder/info_plist.mustache index dca719618..adc1712f2 100644 --- a/lib/jazzy/docset_builder/info_plist.mustache +++ b/lib/jazzy/docset_builder/info_plist.mustache @@ -3,7 +3,7 @@ CFBundleIdentifier - com.jazzy.{{lowercase_name}} + com.jazzy.{{lowercase_safe_name}} CFBundleName {{name}} DocSetPlatformFamily diff --git a/lib/jazzy/source_module.rb b/lib/jazzy/source_module.rb index c9d4991ab..7d3040c77 100644 --- a/lib/jazzy/source_module.rb +++ b/lib/jazzy/source_module.rb @@ -7,28 +7,29 @@ require 'jazzy/source_host' module Jazzy + # A cache of info that is common across all page templating, gathered + # from other parts of the program. class SourceModule + include Config::Mixin + attr_accessor :name attr_accessor :docs attr_accessor :doc_coverage attr_accessor :doc_structure attr_accessor :author_name attr_accessor :author_url - attr_accessor :dash_url + attr_accessor :dash_feed_url attr_accessor :host - def initialize(options, docs, doc_structure, doc_coverage) + def initialize(docs, doc_structure, doc_coverage, docset_builder) self.docs = docs self.doc_structure = doc_structure self.doc_coverage = doc_coverage - self.name = options.module_configs.first.module_name # XXX what actually is this type for - self.author_name = options.author_name - self.author_url = options.author_url - self.host = SourceHost.create(options) - return unless options.dash_url - - self.dash_url = - "dash-feed://#{ERB::Util.url_encode(options.dash_url.to_s)}" + self.name = config.module_names.first # XXX what actually is this type for + self.author_name = config.author_name + self.author_url = config.author_url + self.host = SourceHost.create(config) + self.dash_feed_url = docset_builder.dash_feed_url end def all_declarations diff --git a/spec/integration_specs b/spec/integration_specs index 7f5d8f8c5..483dad5ab 160000 --- a/spec/integration_specs +++ b/spec/integration_specs @@ -1 +1 @@ -Subproject commit 7f5d8f8c5f1ae33d566db4d32800d5f35e76b799 +Subproject commit 483dad5abeec55c9fa24fac79ac6ad4022baa523