From 74cfc898352ea52138754eef797efef6f6edb911 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 15 Dec 2025 22:07:52 +0000 Subject: [PATCH] Change the default theme to Aliki --- .rdoc_options | 2 - AGENTS.md | 9 +- CONTRIBUTING.md | 11 +- LEGAL.rdoc | 6 + README.md | 21 ++- lib/rdoc/options.rb | 2 +- lib/rdoc/rubygems_hook.rb | 6 +- test/rdoc/generator/aliki_test.rb | 250 +++++++++++++++++++++++++ test/rdoc/generator/json_index_test.rb | 8 +- test/rdoc/rdoc_options_test.rb | 10 +- test/rdoc/rdoc_rdoc_test.rb | 2 +- test/rdoc/rdoc_rubygems_hook_test.rb | 2 +- 12 files changed, 302 insertions(+), 27 deletions(-) create mode 100644 test/rdoc/generator/aliki_test.rb diff --git a/.rdoc_options b/.rdoc_options index ab54556487..be43dcd553 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -4,8 +4,6 @@ main_page: README.md autolink_excluded_words: - RDoc -generator_name: aliki - exclude: - AGENTS.md - CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index ccad3629df..c984184190 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -146,10 +146,13 @@ lib/rdoc/ │ ├── prism_ruby.rb # Prism-based Ruby parser │ └── ... ├── generator/ # Documentation generators -│ ├── darkfish.rb # HTML generator (default theme) +│ ├── aliki.rb # HTML generator (default theme) +│ ├── darkfish.rb # HTML generator (deprecated, will be removed in v8.0) │ ├── markup.rb # Markup format generator │ ├── ri.rb # RI command generator -│ └── template/darkfish/ # ERB templates (.rhtml files) +│ └── template/ # ERB templates (.rhtml files) +│ ├── aliki/ # Aliki theme (default) +│ └── darkfish/ # Darkfish theme (deprecated) ├── markup/ # Markup parsing and formatting ├── code_object/ # AST objects for documented items ├── markdown/ # Markdown parsing @@ -196,7 +199,7 @@ exe/ ### Pluggable System - **Parsers:** Ruby, C, Markdown, RD, Prism-based Ruby (experimental) -- **Generators:** HTML/Darkfish, RI, POT (gettext), JSON, Markup +- **Generators:** HTML/Aliki (default), HTML/Darkfish (deprecated), RI, POT (gettext), JSON, Markup ## Common Workflows diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 97eede6632..6cff518bde 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -136,8 +136,8 @@ bundle exec rake coverage RDoc ships with two HTML themes: -- **Aliki** - Modern theme with improved styling and navigation (will become the default) -- **Darkfish** - Classic theme (entering maintenance mode) +- **Aliki** (default) - Modern theme with improved styling and navigation +- **Darkfish** (deprecated) - Classic theme, will be removed in v8.0 New feature development should focus on the Aliki theme. Darkfish will continue to receive bug fixes but no new features. @@ -156,12 +156,13 @@ lib/rdoc/ │ ├── prism_ruby.rb # Prism-based Ruby parser │ └── ... ├── generator/ # Documentation generators -│ ├── darkfish.rb # HTML generator (default theme) +│ ├── aliki.rb # HTML generator (default theme) +│ ├── darkfish.rb # HTML generator (deprecated, will be removed in v8.0) │ ├── markup.rb # Markup format generator │ ├── ri.rb # RI command generator │ └── template/ # ERB templates -│ ├── darkfish/ # Darkfish theme (maintenance mode) -│ └── aliki/ # Aliki theme (active development) +│ ├── aliki/ # Aliki theme (default) +│ └── darkfish/ # Darkfish theme (deprecated) ├── markup/ # Markup parsing and formatting ├── code_object/ # AST objects for documented items ├── markdown.kpeg # Parser source (edit this) diff --git a/LEGAL.rdoc b/LEGAL.rdoc index dae1059161..f054ab851a 100644 --- a/LEGAL.rdoc +++ b/LEGAL.rdoc @@ -4,6 +4,12 @@ The files in this distribution are covered by the Ruby license (see LICENSE) except the features mentioned below: +Aliki:: + Aliki was written by Stan Lo and is included under the MIT license. + + * lib/rdoc/generator/aliki.rb + * lib/rdoc/generator/template/aliki/* + Darkfish:: Darkfish was written by Michael Granger and is included under the BSD 3-Clause license. Darkfish contains images from the Silk Icons set by Mark James. diff --git a/README.md b/README.md index f994820832..9eecfc0d0d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ require 'rdoc/rdoc' options = RDoc::Options.new options.files = ['a.rb', 'b.rb'] -options.setup_generator 'darkfish' +options.setup_generator 'aliki' # see RDoc::Options rdoc = RDoc::RDoc.new @@ -90,7 +90,24 @@ To determine how well your project is documented run `rdoc -C lib` to get a docu ## Theme Options -There are a few community-maintained themes for RDoc: +RDoc ships with two built-in themes: + +- **Aliki** (default) - A modern, clean theme with improved navigation and search +- **Darkfish** (deprecated) - The classic theme, will be removed in v8.0 + +To use the Darkfish theme instead of the default Aliki theme: + +```shell +rdoc --format darkfish +``` + +Or in your `.rdoc_options` file: + +```yaml +generator_name: darkfish +``` + +There are also a few community-maintained themes for RDoc: - [rorvswild-theme-rdoc](https://github.com/BaseSecrete/rorvswild-theme-rdoc) - [hanna](https://github.com/jeremyevans/hanna) (a fork maintained by [Jeremy Evans](https://github.com/jeremyevans)) diff --git a/lib/rdoc/options.rb b/lib/rdoc/options.rb index 557993a263..c3f16d37b1 100644 --- a/lib/rdoc/options.rb +++ b/lib/rdoc/options.rb @@ -411,7 +411,7 @@ def init_ivars # :nodoc: @files = nil @force_output = false @force_update = true - @generator_name = "darkfish" + @generator_name = "aliki" @generators = RDoc::RDoc::GENERATORS @generator_options = [] @hyperlink_all = false diff --git a/lib/rdoc/rubygems_hook.rb b/lib/rdoc/rubygems_hook.rb index a1216d346d..448afc9a2f 100644 --- a/lib/rdoc/rubygems_hook.rb +++ b/lib/rdoc/rubygems_hook.rb @@ -118,7 +118,7 @@ def delete_legacy_args(args) end ## - # Generates documentation using the named +generator+ ("darkfish" or "ri") + # Generates documentation using the named +generator+ ("aliki" or "ri") # and following the given +options+. # # Documentation will be generated into +destination+ @@ -190,7 +190,7 @@ def generate Dir.chdir @spec.full_gem_path do # RDoc::Options#finish must be called before parse_files. - # RDoc::Options#finish is also called after ri/darkfish generator setup. + # RDoc::Options#finish is also called after ri/aliki generator setup. # We need to dup the options to avoid modifying it after finish is called. parse_options = options.dup parse_options.finish @@ -202,7 +202,7 @@ def generate document 'ri', options, @ri_dir if @generate_ri and (@force or not File.exist? @ri_dir) - document 'darkfish', options, @rdoc_dir if + document 'aliki', options, @rdoc_dir if @generate_rdoc and (@force or not File.exist? @rdoc_dir) end diff --git a/test/rdoc/generator/aliki_test.rb b/test/rdoc/generator/aliki_test.rb new file mode 100644 index 0000000000..8d3ba737c4 --- /dev/null +++ b/test/rdoc/generator/aliki_test.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true +require_relative '../helper' + +class RDocGeneratorAlikiTest < RDoc::TestCase + + def setup + super + + @lib_dir = "#{@pwd}/lib" + $LOAD_PATH.unshift @lib_dir + + @options = RDoc::Options.new + @options.option_parser = OptionParser.new + + @tmpdir = File.join Dir.tmpdir, "test_rdoc_generator_aliki_#{$$}" + FileUtils.mkdir_p @tmpdir + Dir.chdir @tmpdir + @options.op_dir = @tmpdir + @options.generator = RDoc::Generator::Aliki + + $LOAD_PATH.each do |path| + aliki_dir = File.join path, 'rdoc/generator/template/aliki/' + next unless File.directory? aliki_dir + @options.template_dir = aliki_dir + break + end + + @rdoc.options = @options + + @g = @options.generator.new @store, @options + @rdoc.generator = @g + + @top_level = @store.add_file 'file.rb' + @top_level.parser = RDoc::Parser::Ruby + @klass = @top_level.add_class RDoc::NormalClass, 'Klass' + + @meth = RDoc::AnyMethod.new nil, 'method' + @meth_with_html_tag_yield = RDoc::AnyMethod.new nil, 'method_with_html_tag_yield' + @meth_with_html_tag_yield.block_params = '%<>, yield_arg' + + @klass.add_method @meth + @klass.add_method @meth_with_html_tag_yield + + @store.complete :private + end + + def teardown + super + + $LOAD_PATH.shift + Dir.chdir @pwd + FileUtils.rm_rf @tmpdir + end + + def test_inheritance_and_template_dir + assert_kind_of RDoc::Generator::Darkfish, @g + assert_match %r{/template/aliki\z}, @g.template_dir.to_s + end + + def test_write_style_sheet_copies_css_and_js_only + @g.generate + + # Aliki should have these assets + assert_file 'css/rdoc.css' + assert_file 'js/aliki.js' + assert_file 'js/search.js' + assert_file 'js/theme-toggle.js' + assert_file 'js/c_highlighter.js' + + # Aliki should NOT have fonts (unlike Darkfish) + refute File.exist?('css/fonts.css'), 'Aliki should not copy fonts.css' + refute File.exist?('fonts'), 'Aliki should not copy fonts directory' + end + + # Aliki-specific: verify version query strings on asset references + def test_asset_version_query_strings + @g.generate + + content = File.binread('index.html') + + # CSS should have version query string + assert_match %r{css/rdoc\.css\?v=#{Regexp.escape(RDoc::VERSION)}}, content + + # JS files should have version query strings + assert_match %r{js/aliki\.js\?v=#{Regexp.escape(RDoc::VERSION)}}, content + assert_match %r{js/search\.js\?v=#{Regexp.escape(RDoc::VERSION)}}, content + assert_match %r{js/theme-toggle\.js\?v=#{Regexp.escape(RDoc::VERSION)}}, content + end + + def test_open_graph_meta_tags_for_index + @options.title = "My Ruby Project" + @g.generate + + content = File.binread('index.html') + + assert_match %r{}, content + assert_match %r{}, content + assert_match %r{}, content + end + + # Aliki-specific: Twitter meta tags + def test_twitter_meta_tags_for_index + @options.title = "My Ruby Project" + @g.generate + + content = File.binread('index.html') + + assert_match %r{}, content + assert_match %r{}, content + assert_match %r{}, content + assert_match %r{}, content + end + + def test_meta_tags_multiline_format + top_level = @store.add_file 'file.rb' + top_level.add_class @klass.class, @klass.name + inner = @klass.add_class RDoc::NormalClass, 'Inner' + inner.add_comment "This is a normal class.", top_level + + @g.generate + + content = File.binread('Klass/Inner.html') + + # Aliki formats meta tags across multiple lines + assert_match %r{name="keywords"\s+content="ruby,class,Klass::Inner"}m, content + assert_match %r{name="description"\s+content="class Klass::Inner: This is a normal class\."}m, content + end + + def test_template_stylesheets_with_version + css = Tempfile.create(%W[custom .css], Dir.mktmpdir('tmp', '.')) + File.write(css, '') + css.close + base = File.basename(css) + + @options.template_stylesheets << css + + @g.generate + + assert_file base + # Aliki includes version in query string for custom stylesheets too + assert_match %r{href="\./#{Regexp.escape(base)}\?v=#{Regexp.escape(RDoc::VERSION)}"}, File.binread('index.html') + end + + def test_generated_method_with_html_tag_yield_escapes_xss + top_level = @store.add_file 'file.rb' + top_level.add_class @klass.class, @klass.name + + @g.generate + + content = File.binread('Klass.html') + + # Script tags in yield params should be escaped + assert_match %r{%<<script>alert\("atui"\)</script>>}, content + refute_match %r{}, content + end + + def test_title_escape_prevents_xss + @options.title = '' + @g.generate + + content = File.binread('index.html') + + # Title should be HTML escaped + assert_match %r{<script>alert\("xss"\)</script>}, content + refute_match %r{<script>alert}, content + end + + def test_generate + @klass.add_class RDoc::NormalClass, 'Inner' + @klass.add_comment "Test class documentation", @top_level + + @g.generate + + # Core HTML files + assert_file 'index.html' + assert_file 'Klass.html' + assert_file 'Klass/Inner.html' + + # Aliki assets + assert_file 'js/search_index.js' + assert_file 'css/rdoc.css' + assert_file 'js/aliki.js' + + # Verify HTML structure + index = File.binread('index.html') + assert_match %r{<html lang="en">}, index + assert_match %r{<body role="document"}, index + assert_match %r{<nav id="navigation" role="navigation">}, index + assert_match %r{<main role="main">}, index + end + + def test_canonical_url + @klass.add_class RDoc::NormalClass, 'Inner' + @store.options.canonical_root = @options.canonical_root = "https://example.com/docs/" + @g.generate + + index_content = File.binread('index.html') + assert_include index_content, '<link rel="canonical" href="https://example.com/docs/">' + + # Open Graph should also include canonical URL + assert_match %r{<meta property="og:url" content="https://example\.com/docs/">}, index_content + + inner_content = File.binread('Klass/Inner.html') + assert_include inner_content, '<link rel="canonical" href="https://example.com/docs/Klass/Inner.html">' + end + + def test_dry_run_creates_no_files + @g.dry_run = true + + @g.generate + + refute_file 'index.html' + refute_file 'css/rdoc.css' + refute_file 'js/aliki.js' + end + + # Test locale affects html lang attribute + def test_html_lang_from_locale + @options.locale = RDoc::I18n::Locale.new 'ja' + @g.generate + + content = File.binread('index.html') + assert_include content, '<html lang="ja">' + end +end diff --git a/test/rdoc/generator/json_index_test.rb b/test/rdoc/generator/json_index_test.rb index 875f114b31..6157b49881 100644 --- a/test/rdoc/generator/json_index_test.rb +++ b/test/rdoc/generator/json_index_test.rb @@ -8,20 +8,20 @@ class RDocGeneratorJsonIndexTest < RDoc::TestCase def setup super - @tmpdir = Dir.mktmpdir "test_rdoc_generator_darkfish_#{$$}_" + @tmpdir = Dir.mktmpdir "test_rdoc_generator_aliki_#{$$}_" FileUtils.mkdir_p @tmpdir @options = RDoc::Options.new @options.files = [] # JsonIndex is used in conjunction with another generator - @options.setup_generator 'darkfish' + @options.setup_generator 'aliki' @options.template_dir = '' @options.op_dir = @tmpdir @options.option_parser = OptionParser.new @options.finish - @darkfish = RDoc::Generator::Darkfish.new @store, @options - @g = RDoc::Generator::JsonIndex.new @darkfish, @options + @aliki = RDoc::Generator::Aliki.new @store, @options + @g = RDoc::Generator::JsonIndex.new @aliki, @options @rdoc.options = @options @rdoc.generator = @g diff --git a/test/rdoc/rdoc_options_test.rb b/test/rdoc/rdoc_options_test.rb index 31da288023..be87b39d78 100644 --- a/test/rdoc/rdoc_options_test.rb +++ b/test/rdoc/rdoc_options_test.rb @@ -276,9 +276,9 @@ def test_parse_default @options.parse [] @options.finish - assert_equal RDoc::Generator::Darkfish, @options.generator - assert_equal 'darkfish', @options.template - assert_match %r%rdoc/generator/template/darkfish$%, @options.template_dir + assert_equal RDoc::Generator::Aliki, @options.generator + assert_equal 'aliki', @options.template + assert_match %r%rdoc/generator/template/aliki$%, @options.template_dir end def test_parse_deprecated @@ -653,8 +653,8 @@ def test_parse_template_nonexistent assert_equal "could not find template NONEXISTENT\n", err @options.finish - assert_equal 'darkfish', @options.template - assert_match %r%rdoc/generator/template/darkfish$%, @options.template_dir + assert_equal 'aliki', @options.template + assert_match %r%rdoc/generator/template/aliki$%, @options.template_dir end def test_parse_template_load_path diff --git a/test/rdoc/rdoc_rdoc_test.rb b/test/rdoc/rdoc_rdoc_test.rb index 642e69074b..f75939cb2d 100644 --- a/test/rdoc/rdoc_rdoc_test.rb +++ b/test/rdoc/rdoc_rdoc_test.rb @@ -43,7 +43,7 @@ def test_document # functional test def test_document_with_dry_run # functional test options = RDoc::Options.new options.files = [File.expand_path('../xref_data.rb', __FILE__)] - options.setup_generator 'darkfish' + options.setup_generator 'aliki' options.main_page = 'MAIN_PAGE.rdoc' options.root = Pathname File.expand_path('..', __FILE__) options.title = 'title' diff --git a/test/rdoc/rdoc_rubygems_hook_test.rb b/test/rdoc/rdoc_rubygems_hook_test.rb index 99194c59d8..bb122f0e41 100644 --- a/test/rdoc/rdoc_rubygems_hook_test.rb +++ b/test/rdoc/rdoc_rubygems_hook_test.rb @@ -98,7 +98,7 @@ def test_document @hook.instance_variable_set :@rdoc, rdoc @hook.instance_variable_set :@file_info, [] - @hook.document 'darkfish', options, @a.doc_dir('rdoc') + @hook.document 'aliki', options, @a.doc_dir('rdoc') assert @hook.rdoc_installed? end