From bf4e901c014c647d3ef603847b2c86da82aaafaa Mon Sep 17 00:00:00 2001 From: Calum Smith Date: Mon, 8 Sep 2025 17:14:56 -0400 Subject: [PATCH 01/11] Add styles for syntax diagrams in Rust reference --- assets/stylesheets/pages/_rust.scss | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/assets/stylesheets/pages/_rust.scss b/assets/stylesheets/pages/_rust.scss index d1a181d963..47cc09ca2d 100644 --- a/assets/stylesheets/pages/_rust.scss +++ b/assets/stylesheets/pages/_rust.scss @@ -18,4 +18,73 @@ float: right; margin-left: .5rem; } + + .grammar-container { @extend %note, %note-gray; } + + /* Railroad styles from: + * https://github.com/rust-lang/reference/blob/f82156b8c3a784158ce609bebfa3a77b5ae8a5ed/theme/reference.css#L683-L734 + * Plus CSS variables inheriting from DevDocs variables + */ + + svg.railroad { + --railroad-background-color: var(--boxBackground); + --railroad-background-image: + linear-gradient(to right, rgb(from currentColor r g b / 0.1) 1px, transparent 1px), + linear-gradient(to bottom, rgb(from currentColor r g b / 0.1) 1px, transparent 1px); + --railroad-path-stroke: currentColor; + --railroad-rect-stroke: currentColor; + --railroad-rect-fill: var(--noteBackground); + --railroad-text-fill: currentColor; + + background-color: var(--railroad-background-color); + background-size: 15px 15px; + background-image: var(--railroad-background-image); + } + + svg.railroad rect.railroad_canvas { + stroke-width: 0px; + fill: none; + } + + svg.railroad path { + stroke-width: 3px; + stroke: var(--railroad-path-stroke); + fill: none; + } + + svg.railroad .debug { + stroke-width: 1px; + stroke: red; + } + + svg.railroad text { + font: 14px monospace; + text-anchor: middle; + fill: var(--railroad-text-fill); + } + + svg.railroad .nonterminal text { + font-weight: bold; + } + + svg.railroad text.comment { + font: italic 12px monospace; + } + + svg.railroad rect { + stroke-width: 3px; + stroke: var(--railroad-rect-stroke); + fill: var(--railroad-rect-fill); + } + + svg.railroad g.labeledbox>rect { + stroke-width: 1px; + stroke: grey; + stroke-dasharray: 5px; + fill: rgba(90, 90, 150, .1); + } + + svg.railroad g.exceptbox > rect { + fill:rgba(245, 160, 125, .1); + } } From d877a45e8cb9f9cf2c6099af39aea6bf1eb486fa Mon Sep 17 00:00:00 2001 From: Calum Smith Date: Mon, 8 Sep 2025 17:15:33 -0400 Subject: [PATCH 02/11] Avoid stripping some empty SVG elements --- lib/docs/filters/core/clean_text.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/docs/filters/core/clean_text.rb b/lib/docs/filters/core/clean_text.rb index 181ab638c4..1deeba01ff 100644 --- a/lib/docs/filters/core/clean_text.rb +++ b/lib/docs/filters/core/clean_text.rb @@ -2,7 +2,7 @@ module Docs class CleanTextFilter < Filter - EMPTY_NODES_RGX = /<(?!td|th|iframe|mspace)(\w+)[^>]*>[[:space:]]*<\/\1>/ + EMPTY_NODES_RGX = /<(?!td|th|iframe|mspace|rect|path|ellipse|line|polyline)(\w+)[^>]*>[[:space:]]*<\/\1>/ def call return html if context[:clean_text] == false From e1e9f8bdd407a50ee3791dab54729e1d0a5b0e8c Mon Sep 17 00:00:00 2001 From: Calum Smith Date: Mon, 8 Sep 2025 17:17:35 -0400 Subject: [PATCH 03/11] Update rustc error codes URL, and improve Rust logic for page names Fixes freeCodeCamp/devdocs#2568 --- lib/docs/filters/rust/clean_html.rb | 4 ++-- lib/docs/filters/rust/entries.rb | 27 ++++++++++++++++----------- lib/docs/scrapers/rust.rb | 5 +++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/lib/docs/filters/rust/clean_html.rb b/lib/docs/filters/rust/clean_html.rb index d2049956cd..72fb518729 100644 --- a/lib/docs/filters/rust/clean_html.rb +++ b/lib/docs/filters/rust/clean_html.rb @@ -4,9 +4,9 @@ module Docs class Rust class CleanHtmlFilter < Filter def call - if slug.start_with?('book') || slug.start_with?('reference') + if slug.start_with?('book') || slug.start_with?('reference') || slug.start_with?('error_codes') @doc = at_css('#content main') - elsif slug == 'error-index' + elsif slug.start_with?('error_codes') css('.error-undescribed').remove css('.error-described').each do |node| diff --git a/lib/docs/filters/rust/entries.rb b/lib/docs/filters/rust/entries.rb index 31fa47ba9a..5a60aae785 100644 --- a/lib/docs/filters/rust/entries.rb +++ b/lib/docs/filters/rust/entries.rb @@ -3,13 +3,22 @@ class Rust class EntriesFilter < Docs::EntriesFilter def get_name - if slug.start_with?('book') || slug.start_with?('reference') - name = at_css("h2", "h1") - ch1 = slug[/ch(\d+)-(\d+)/, 1] - ch2 = slug[/ch(\d+)-(\d+)/, 2] + if slug.start_with?('book') + name = at_css('main h1', 'main h2') + + if slug.start_with?('book/appendix') + return name ? name.content : 'Appendix' + end + + ch1 = slug[/ch(\d+)-(\d+)/, 1] || '00' + ch2 = slug[/ch(\d+)-(\d+)/, 2] || '00' name ? "#{ch1}.#{ch2}. #{name.content}" : 'Introduction' - elsif slug == 'error-index' + elsif slug.start_with?('reference') + at_css('main h1').content + elsif slug == 'error_codes/error-index' 'Compiler Errors' + elsif slug.start_with?('error_codes') + slug.split('/').last.upcase else at_css('main h1').at_css('button')&.remove name = at_css('main h1').content.remove(/\A.+\s/).remove('⎘') @@ -26,7 +35,7 @@ def get_type 'Guide' elsif slug.start_with?('reference') 'Reference' - elsif slug == 'error-index' + elsif slug.start_with?('error_codes') 'Compiler Errors' else path = name.split('::') @@ -40,12 +49,8 @@ def get_type end def additional_entries - if slug.start_with?('book') || slug.start_with?('reference') + if slug.start_with?('book') || slug.start_with?('reference') || slug.start_with?('error_codes') [] - elsif slug == 'error-index' - css('.error-described h2.section-header').each_with_object [] do |node, entries| - entries << [node.content, node['id']] unless node.content.include?('Note:') - end else css('.method') .each_with_object({}) { |node, entries| diff --git a/lib/docs/scrapers/rust.rb b/lib/docs/scrapers/rust.rb index 8ccdaea376..15ce27c23c 100644 --- a/lib/docs/scrapers/rust.rb +++ b/lib/docs/scrapers/rust.rb @@ -9,7 +9,7 @@ class Rust < UrlScraper self.initial_paths = %w( reference/introduction.html std/index.html - error-index.html) + error_codes/error-index.html) self.links = { home: 'https://www.rust-lang.org/', code: 'https://github.com/rust-lang/rust' @@ -21,7 +21,8 @@ class Rust < UrlScraper /\Abook\//, /\Areference\//, /\Acollections\//, - /\Astd\// ] + /\Astd\//, + /\Aerror_codes\//, ] options[:skip] = %w(book/README.html book/ffi.html) options[:skip_patterns] = [/(? Date: Mon, 8 Sep 2025 17:18:11 -0400 Subject: [PATCH 04/11] Remove visible anchor links from Rust Reference --- lib/docs/filters/rust/clean_html.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/docs/filters/rust/clean_html.rb b/lib/docs/filters/rust/clean_html.rb index 72fb518729..14653477b1 100644 --- a/lib/docs/filters/rust/clean_html.rb +++ b/lib/docs/filters/rust/clean_html.rb @@ -32,6 +32,8 @@ def call css('.doc-anchor').remove + css('.rule-link').remove + # Fix notable trait sections css('.method, .rust.trait').each do |node| traitSection = node.at_css('.notable-traits') From 1bebe5b890fa1120e445a42962c3b8801bf09574 Mon Sep 17 00:00:00 2001 From: Calum Smith Date: Mon, 8 Sep 2025 17:18:56 -0400 Subject: [PATCH 05/11] Clean up syntax diagrams in Rust Reference, put in `
` --- lib/docs/filters/rust/clean_html.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/docs/filters/rust/clean_html.rb b/lib/docs/filters/rust/clean_html.rb index 14653477b1..5a7e69df20 100644 --- a/lib/docs/filters/rust/clean_html.rb +++ b/lib/docs/filters/rust/clean_html.rb @@ -57,6 +57,20 @@ def call node.before(node.children).remove end + css('button.grammar-toggle-railroad').remove + css('.grammar-container').each do |node| + next_element = node.next_element + if next_element && next_element['class'] && next_element['class'].include?('grammar-railroad') + next_element.remove + node.add_child(next_element) + end + end + + css('.grammar-railroad').each do |node| + node.name = 'details' + node.prepend_child("Syntax diagram") + end + css('a.header').each do |node| unless node.first_element_child.nil? node.first_element_child['id'] = node['name'] || node['id'] From 97f7b5ad5956cd85413c36b9062e64544d54086d Mon Sep 17 00:00:00 2001 From: Calum Smith Date: Mon, 8 Sep 2025 17:34:40 -0400 Subject: [PATCH 06/11] Preserve whitespace in Rust Reference syntax definitions --- lib/docs/filters/rust/clean_html.rb | 6 ++++++ lib/docs/scrapers/rust.rb | 2 ++ 2 files changed, 8 insertions(+) diff --git a/lib/docs/filters/rust/clean_html.rb b/lib/docs/filters/rust/clean_html.rb index 5a7e69df20..1b5b458988 100644 --- a/lib/docs/filters/rust/clean_html.rb +++ b/lib/docs/filters/rust/clean_html.rb @@ -64,6 +64,12 @@ def call next_element.remove node.add_child(next_element) end + + # We changed this to a
 in parse(), changing it back here
+          node.name = 'div'
+          node.css('.grammar-literal').each do |literal|
+            literal.name = 'code'
+          end
         end
 
         css('.grammar-railroad').each do |node|
diff --git a/lib/docs/scrapers/rust.rb b/lib/docs/scrapers/rust.rb
index 15ce27c23c..74dab4443e 100644
--- a/lib/docs/scrapers/rust.rb
+++ b/lib/docs/scrapers/rust.rb
@@ -57,6 +57,8 @@ def process_response?(response)
 
     def parse(response) # Hook here because Nokogori removes whitespace from headings
       response.body.gsub! %r{}, '
'
+      # And the reference uses whitespace for indentation in grammar definitions
+      response.body.gsub! %r{
([\W\w]+?)
}, '
\1
' super end end From d777a8a3fb99bc16b1a08c6bc3529eb65e2ce526 Mon Sep 17 00:00:00 2001 From: Calum Smith Date: Tue, 9 Sep 2025 12:21:46 -0400 Subject: [PATCH 07/11] Remove unnecessary `onclick` attributes in Rust docs --- lib/docs/filters/rust/clean_html.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/docs/filters/rust/clean_html.rb b/lib/docs/filters/rust/clean_html.rb index 1b5b458988..748d3ffbfd 100644 --- a/lib/docs/filters/rust/clean_html.rb +++ b/lib/docs/filters/rust/clean_html.rb @@ -65,6 +65,10 @@ def call node.add_child(next_element) end + node.css('[onclick="show_railroad()"]').each do |subnode| + subnode.remove_attribute('onclick') + end + # We changed this to a
 in parse(), changing it back here
           node.name = 'div'
           node.css('.grammar-literal').each do |literal|

From ee0e347ed322c8f89e74dd5bb92bad9400df350b Mon Sep 17 00:00:00 2001
From: Calum Smith 
Date: Tue, 9 Sep 2025 12:22:40 -0400
Subject: [PATCH 08/11] =?UTF-8?q?Normalize=20xlink:href=20attributes=20lik?=
 =?UTF-8?q?e=20HTML=20href=20ones=E2=80=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

…so links inside SVGs, such as those in Rust's railroad diagrams, can
work properly.
---
 lib/docs/filters/core/normalize_paths.rb | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/lib/docs/filters/core/normalize_paths.rb b/lib/docs/filters/core/normalize_paths.rb
index 9f182f8d5e..431c781a06 100644
--- a/lib/docs/filters/core/normalize_paths.rb
+++ b/lib/docs/filters/core/normalize_paths.rb
@@ -7,8 +7,11 @@ def call
       result[:store_path] = store_path
 
       css('a').each do |link|
-        next unless (href = link['href']) && relative_url_string?(href)
-        link['href'] = normalize_href(href)
+        href = link['href']
+        link['href'] = normalize_href(href) if href && relative_url_string?(href)
+
+        xlink_href = link['xlink:href']
+        link['xlink:href'] = normalize_href(xlink_href) if xlink_href && relative_url_string?(xlink_href)
       end
 
       doc

From dd2c3e5eb2d5a78c57cc5643638d1ae0406f2656 Mon Sep 17 00:00:00 2001
From: Calum Smith 
Date: Tue, 9 Sep 2025 14:30:13 -0400
Subject: [PATCH 09/11] Make internal navigation handle ``s in SVGs

---
 assets/javascripts/lib/page.js | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)

diff --git a/assets/javascripts/lib/page.js b/assets/javascripts/lib/page.js
index f368fbbab3..ea9c3d1a80 100644
--- a/assets/javascripts/lib/page.js
+++ b/assets/javascripts/lib/page.js
@@ -271,13 +271,25 @@ var onclick = function (event) {
   }
 
   let link = $.eventTarget(event);
-  while (link && link.tagName !== "A") {
+  while (link && !(link.tagName === "A" || link.tagName === "a")) {
     link = link.parentNode;
   }
 
-  if (link && !link.target && isSameOrigin(link.href)) {
+  if (!link) return;
+
+  // If the `` is in an SVG, its attributes are `SVGAnimatedString`s
+  // instead of strings
+  let href = link.href instanceof SVGAnimatedString
+    ? new URL(link.href.baseVal, location.href).href
+    : link.href;
+  let target = link.target instanceof SVGAnimatedString
+    ? link.target.baseVal
+    : link.target;
+
+  if (!target && isSameOrigin(href)) {
     event.preventDefault();
-    let path = link.pathname + link.search + link.hash;
+    let parsedHref = new URL(href);
+    let path = parsedHref.pathname + parsedHref.search + parsedHref.hash;
     path = path.replace(/^\/\/+/, "/"); // IE11 bug
     page.show(path);
   }

From d6f16be79bf7d42f9a78c765995fb7718656d4cf Mon Sep 17 00:00:00 2001
From: Calum Smith 
Date: Tue, 9 Sep 2025 14:37:08 -0400
Subject: [PATCH 10/11] =?UTF-8?q?When=20navigating=20to=20a=20fragment=20i?=
 =?UTF-8?q?nside=20a=20`
`,=20open=20it=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …this matches browsers' native behaviour. --- assets/javascripts/lib/util.js | 10 ++++++++++ assets/javascripts/views/content/content.js | 1 + 2 files changed, 11 insertions(+) diff --git a/assets/javascripts/lib/util.js b/assets/javascripts/lib/util.js index 5226262d0c..1cb62b8c9c 100644 --- a/assets/javascripts/lib/util.js +++ b/assets/javascripts/lib/util.js @@ -353,6 +353,16 @@ $.lockScroll = function (el, fn) { } }; +// If `el` is inside any `
` elements, expand them. +$.openDetailsAncestors = function (el) { + while (el) { + if (el.tagName === "DETAILS") { + el.open = true; + } + el = el.parentElement; + } +} + let smoothScroll = (smoothStart = smoothEnd = diff --git a/assets/javascripts/views/content/content.js b/assets/javascripts/views/content/content.js index 61bccb102f..875f96b4c6 100644 --- a/assets/javascripts/views/content/content.js +++ b/assets/javascripts/views/content/content.js @@ -114,6 +114,7 @@ app.views.Content = class Content extends app.View { $.scrollToWithImageLock(el, this.scrollEl, "top", { margin: this.scrollEl === this.el ? 0 : $.offset(this.el).top, }); + $.openDetailsAncestors(el); $.highlight(el, { className: "_highlight" }); } else { this.scrollTo(this.scrollMap[this.routeCtx.state.id]); From 8f39593118c2152320242850cdad01b41733d604 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Thu, 25 Sep 2025 18:06:13 +0200 Subject: [PATCH 11/11] Update Rust documentation (1.90.0) --- lib/docs/scrapers/rust.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/docs/scrapers/rust.rb b/lib/docs/scrapers/rust.rb index 74dab4443e..2ad145d372 100644 --- a/lib/docs/scrapers/rust.rb +++ b/lib/docs/scrapers/rust.rb @@ -3,7 +3,7 @@ module Docs class Rust < UrlScraper self.type = 'rust' - self.release = '1.88.0' + self.release = '1.90.0' self.base_url = 'https://doc.rust-lang.org/' self.root_path = 'book/index.html' self.initial_paths = %w(