diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee
index 4eaa4f6d..ec59971d 100644
--- a/assets/javascripts/templates/pages/about_tmpl.coffee
+++ b/assets/javascripts/templates/pages/about_tmpl.coffee
@@ -794,9 +794,9 @@ credits = [
'https://raw.githubusercontent.com/sass/sass/stable/MIT-LICENSE'
], [
'Scala',
- '2002-2019 EPFL, with contributions from Lightbend',
+ '2002-2022 EPFL, with contributions from Lightbend',
'Apache',
- 'https://raw.githubusercontent.com/scala/scala-lang/master/license.md'
+ 'https://www.scala-lang.org/license/'
], [
'scikit-image',
'2019 the scikit-image team',
diff --git a/assets/javascripts/vendor/prism.js b/assets/javascripts/vendor/prism.js
index 4c201baf..33d7acf3 100644
--- a/assets/javascripts/vendor/prism.js
+++ b/assets/javascripts/vendor/prism.js
@@ -1,5 +1,5 @@
-/* PrismJS 1.26.0
-https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+c+cpp+cmake+coffeescript+crystal+d+dart+diff+django+elixir+erlang+go+groovy+java+json+julia+kotlin+latex+lua+markup-templating+matlab+nginx+nim+ocaml+perl+php+python+qml+r+jsx+ruby+rust+scss+shell-session+sql+typescript+yaml+zig */
+/* PrismJS 1.27.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+bash+c+cpp+cmake+coffeescript+crystal+d+dart+diff+django+elixir+erlang+go+groovy+java+json+julia+kotlin+latex+lua+markup-templating+matlab+nginx+nim+ocaml+perl+php+python+qml+r+jsx+ruby+rust+scss+scala+shell-session+sql+typescript+yaml+zig */
///
var _self = (typeof window !== 'undefined')
@@ -4660,6 +4660,56 @@ Prism.languages.insertBefore('scss', 'function', {
Prism.languages.scss['atrule'].inside.rest = Prism.languages.scss;
+Prism.languages.scala = Prism.languages.extend('java', {
+ 'triple-quoted-string': {
+ pattern: /"""[\s\S]*?"""/,
+ greedy: true,
+ alias: 'string'
+ },
+ 'string': {
+ pattern: /("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,
+ greedy: true
+ },
+ 'keyword': /<-|=>|\b(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|null|object|override|package|private|protected|return|sealed|self|super|this|throw|trait|try|type|val|var|while|with|yield)\b/,
+ 'number': /\b0x(?:[\da-f]*\.)?[\da-f]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e\d+)?[dfl]?/i,
+ 'builtin': /\b(?:Any|AnyRef|AnyVal|Boolean|Byte|Char|Double|Float|Int|Long|Nothing|Short|String|Unit)\b/,
+ 'symbol': /'[^\d\s\\]\w*/
+});
+
+Prism.languages.insertBefore('scala', 'triple-quoted-string', {
+ 'string-interpolation': {
+ pattern: /\b[a-z]\w*(?:"""(?:[^$]|\$(?:[^{]|\{(?:[^{}]|\{[^{}]*\})*\}))*?"""|"(?:[^$"\r\n]|\$(?:[^{]|\{(?:[^{}]|\{[^{}]*\})*\}))*")/i,
+ greedy: true,
+ inside: {
+ 'id': {
+ pattern: /^\w+/,
+ greedy: true,
+ alias: 'function'
+ },
+ 'escape': {
+ pattern: /\\\$"|\$[$"]/,
+ greedy: true,
+ alias: 'symbol'
+ },
+ 'interpolation': {
+ pattern: /\$(?:\w+|\{(?:[^{}]|\{[^{}]*\})*\})/,
+ greedy: true,
+ inside: {
+ 'punctuation': /^\$\{?|\}$/,
+ 'expression': {
+ pattern: /[\s\S]+/,
+ inside: Prism.languages.scala
+ }
+ }
+ },
+ 'string': /[\s\S]+/
+ }
+ }
+});
+
+delete Prism.languages.scala['class-name'];
+delete Prism.languages.scala['function'];
+
(function (Prism) {
// CAREFUL!
diff --git a/assets/stylesheets/pages/_scala.scss b/assets/stylesheets/pages/_scala.scss
index b2beb118..94b8f7b4 100644
--- a/assets/stylesheets/pages/_scala.scss
+++ b/assets/stylesheets/pages/_scala.scss
@@ -1,4 +1,43 @@
._scala {
@extend %simple;
+
.deprecated { @extend %label-red; }
+
+ .attributes dl,
+ .attributes pre {
+ margin: 0;
+ }
+
+ .related-types {
+ @extend %pre;
+ margin: 0;
+ white-space: normal;
+ }
+
+ .links {
+ @extend %box;
+ margin-left: -1rem;
+ text-align: center;
+ padding: .5em;
+
+ a { padding: .4em }
+
+ @include print {
+ display: none;
+ }
+ }
+
+ .source-link {
+ float: right;
+ font-size: .75rem;
+ color: var(--linkColor);
+ cursor: pointer;
+ @extend %user-select-none;
+
+ &:hover { text-decoration: underline; }
+
+ @include print {
+ display: none;
+ }
+ }
}
diff --git a/lib/docs/filters/scala/clean_html.rb b/lib/docs/filters/scala/clean_html_v2.rb
similarity index 98%
rename from lib/docs/filters/scala/clean_html.rb
rename to lib/docs/filters/scala/clean_html_v2.rb
index 0320932d..05a3ad58 100644
--- a/lib/docs/filters/scala/clean_html.rb
+++ b/lib/docs/filters/scala/clean_html_v2.rb
@@ -1,6 +1,6 @@
module Docs
class Scala
- class CleanHtmlFilter < Filter
+ class CleanHtmlV2Filter < Filter
def call
@doc = at_css('#content')
diff --git a/lib/docs/filters/scala/clean_html_v3.rb b/lib/docs/filters/scala/clean_html_v3.rb
new file mode 100644
index 00000000..28c090fd
--- /dev/null
+++ b/lib/docs/filters/scala/clean_html_v3.rb
@@ -0,0 +1,253 @@
+# frozen_string_literal: true
+
+module Docs
+ class Scala
+ class CleanHtmlV3Filter < Filter
+ def call
+ # Remove unneeded elements
+ css('.documentableFilter, .documentableAnchor, .documentableBrief').remove
+
+ format_title
+ format_signature
+ format_top_links
+ format_metadata
+
+ # Remove the redundant long descriptions on the main page
+ if slug == 'index'
+ css('.contents').remove
+ else
+ format_members
+ end
+
+ simplify_html
+
+ doc
+ end
+
+ private
+
+ # Formats the title of the page
+ def format_title
+ cover_header = at_css('.cover-header')
+ return if cover_header.nil?
+
+ # Add the kind of page to the title
+ icon = cover_header.at_css('.micon')
+ types = {
+ cl: 'Class',
+ ob: 'Object',
+ tr: 'Trait',
+ en: 'Enum',
+ ty: 'Type',
+ pa: 'Package',
+ }
+ type_id = cover_header.at_css('.micon')['class']
+ type_id.remove!('micon ')
+ type_id.remove!('-wc')
+ type = types[type_id.to_sym]
+ name = CGI.escapeHTML cover_header.at_css('h1').text
+
+ # Add the package name
+ package = at_css('.breadcrumbs a:nth-of-type(3)').text
+ package = package + '.' unless name.empty? || package.empty?
+
+ # Replace the title
+ title = root_page? ? 'Package root' : "#{type} #{package}#{name}".strip
+ cover_header.replace "
#{title}
"
+ end
+
+ # Formats the signature block at the top of the page
+ def format_signature
+ signature = at_css('.signature')
+ signature_annotations = signature.at_css('.annotations')
+ signature_annotations.name = 'small' unless signature_annotations.nil?
+ signature.replace "#{signature.inner_html}
"
+ end
+
+ # Formats the top links (companion page, source code)
+ def format_top_links
+ # Companion page (e.g. List ↔ List$)
+ links = []
+ at_css('.attributes').css('dt').each do |dt|
+ next if dt.content.strip != 'Companion:'
+ dd = dt.next_sibling
+
+ companion_link = dd.at_css('a')
+ companion_link.content = "Companion #{companion_link.content}"
+ links.append(companion_link.to_html)
+
+ dt.remove
+ dd.remove
+ end
+
+ # Source code
+ at_css('.attributes').css('dt').each do |dt|
+ next if dt.content.strip != 'Source:'
+ dd = dt.next_sibling
+
+ source_link = dd.at_css('a')
+ source_link.content = 'Source code'
+ links.append(source_link.to_html)
+
+ dt.remove
+ dd.remove
+ end
+
+ # Format the links
+ title = at_css('h1')
+ title.add_next_sibling("#{links.join(' • ')}
")
+ end
+
+ # Metadata about the whole file (e.g. supertypes)
+ def format_metadata
+ # Format the values
+ css('.tabs.single .monospace').each do |node|
+ node.css('> div').each do |div|
+ div['class'] = 'member'
+ end
+
+ node['class'] = 'related-types'
+
+ if node.children.count > 15 # Hide too large lists
+ node.replace "
+ #{node.children.count} types
+ #{node.to_html}
+ "
+ end
+ end
+
+ attributes = at_css('.attributes')
+
+ # Change the HTML structure
+ tabs_names = css('.tabs.single .names .tab')
+ tabs_contents = css('.tabs.single .contents .tab')
+ tabs_names.zip(tabs_contents).each do |name, contents|
+ next if name.content == "Graph"
+
+ attributes.add_child("#{name.content}")
+ attributes.add_child("#{contents.inner_html.strip}")
+ end
+
+ convert_dl_to_table(attributes)
+
+ tabs = at_css('.tabs')
+ tabs.remove unless tabs.nil? || tabs.parent['class'] == 'membersList'
+ end
+
+ # Format the members (methods, values…)
+ def format_members
+ # Section headings
+ css('.cover h2').each do |node|
+ node.name = 'h3'
+ end
+ css('h2:not(#signature)').remove
+ css(
+ '.membersList h3',
+
+ # Custom group headers for which Scaladoc generates invalid HTML
+ # (…
)
+ '.documentableList > h3:empty + p'
+ ).each do |node|
+ node.name = 'h2'
+ node.content = node.content
+ end
+
+ # Individual members
+ css('.documentableElement').each do |element|
+ header = element.at_css('.header')
+ header.name = 'h3'
+
+ id = element['id']
+ element.remove_attribute('id')
+ header['id'] = id unless id.nil?
+
+ annotations = element.at_css('.annotations')
+ annotations.name = 'small'
+ header.prepend_child(annotations)
+
+ # View source
+ element.css('dt').each do |dt|
+ next if dt.content.strip != 'Source:'
+ dd = dt.next_sibling
+
+ source_link = dd.at_css('a')
+ source_link.content = 'Source'
+ source_link['class'] = 'source-link'
+ header.prepend_child(source_link)
+
+ dt.remove
+ dd.remove
+ end
+
+ # Format attributes as a table
+ dl = element.at_css('.attributes')
+ convert_dl_to_table(dl) unless dl.nil?
+
+ # Remove the unnecessary wrapper element
+ element.replace(element.inner_html)
+ end
+
+ # Remove deprecated sections
+ css('.documentableList').each do |list|
+ header = list.at_css('.groupHeader')
+ list.remove if (header.text.downcase.include? 'deprecate' rescue false)
+ end
+
+ # Code blocks
+ css('pre > code').each do |code|
+ pre = code.parent
+ pre['data-language'] = 'scala'
+ pre.inner_html = code.inner_html
+ end
+ end
+
+ # Simplify the HTML structure by removing useless elements
+ def simplify_html
+ # Remove unneeded parts of the document
+ @doc = at_css('#content > div')
+
+ # Remove the useless elements around members
+ css('.documentableList > *').each do |element|
+ element.parent = doc
+ end
+ at_css('.membersList').remove
+
+ # Remove useless classes
+ css('.header, .groupHeader, .cover, .documentableName').each do |element|
+ element.remove_attribute('class')
+ end
+
+ # Remove useless attributes
+ css('[t]').each do |element|
+ element.remove_attribute('t')
+ end
+
+ # Remove useless wrapper elements
+ css('.docs, .doc, .memberDocumentation, span, div:not([class])').each do |element|
+ element.replace(element.children)
+ end
+ end
+
+ def convert_dl_to_table(dl)
+ table = Nokogiri::XML::Node.new('table', doc)
+ table['class'] = 'attributes'
+
+ dl.css('> dt').each do |dt|
+ dd = dt.next_element
+ has_dd = dd.name == 'dd' rescue false
+
+ tr = Nokogiri::XML::Node.new('tr', doc)
+ colspan = has_dd ? '' : ' colspan="2"' # handle without following
+ tr.add_child("#{dt.inner_html.sub(/:$/, '')} | ")
+
+ tr.add_child("#{dd.inner_html} | ") if has_dd
+
+ table.add_child(tr)
+ end
+
+ dl.replace(table)
+ end
+
+ end
+ end
+end
diff --git a/lib/docs/filters/scala/entries.rb b/lib/docs/filters/scala/entries_v2.rb
similarity index 95%
rename from lib/docs/filters/scala/entries.rb
rename to lib/docs/filters/scala/entries_v2.rb
index 5eff47fb..ca3b6d12 100644
--- a/lib/docs/filters/scala/entries.rb
+++ b/lib/docs/filters/scala/entries_v2.rb
@@ -1,6 +1,6 @@
module Docs
class Scala
- class EntriesFilter < Docs::EntriesFilter
+ class EntriesV2Filter < Docs::EntriesFilter
REPLACEMENTS = {
'$eq' => '=',
'$colon' => ':',
@@ -75,12 +75,12 @@ module Docs
# include the companion object.
def package_name
name = package_drop_last(slug_parts)
- name.empty? ? '_root_' : name
+ name.empty? ? 'scala' : name
end
def parent_package
parent = package_drop_last(package_name.split('.'))
- parent.empty? ? '_root_' : parent
+ parent.empty? ? 'scala' : parent
end
def package_drop_last(parts)
diff --git a/lib/docs/filters/scala/entries_v3.rb b/lib/docs/filters/scala/entries_v3.rb
new file mode 100644
index 00000000..62337446
--- /dev/null
+++ b/lib/docs/filters/scala/entries_v3.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+module Docs
+ class Scala
+ class EntriesV3Filter < Docs::EntriesFilter
+ REPLACEMENTS = {
+ '$eq' => '=',
+ '$colon' => ':',
+ '$less' => '<',
+ }
+
+ def get_name
+ if is_package?
+ at_css('.cover-header h1').text
+ else
+ name = slug.split('/').last
+
+ # Some objects have inner objects, show ParentObject$.ChildObject$ instead of ParentObject$$ChildObject$
+ name = name.gsub('$$', '$.')
+
+ REPLACEMENTS.each do |key, value|
+ name = name.gsub(key, value)
+ end
+
+ # If a dollar sign is used as separator between two characters, replace it with a dot
+ name.gsub(/([^$.])\$([^$.])/, '\1.\2')
+ end
+ end
+
+ def get_type
+ # if this entry is for a package, we group the package under the parent package
+ if is_package?
+ parent_package
+ # otherwise, group it under the regular package name
+ else
+ package_name
+ end
+ end
+
+ def include_default_entry?
+ # Ignore package pages
+ at_css('.cover-header .micon.pa').nil?
+ end
+
+ def additional_entries
+ entries = []
+ titles = []
+
+ css(".documentableElement").each do |node|
+ # Ignore elements without IDs
+ id = node['id']
+ next if id.nil?
+
+ # Ignore deprecated and inherited members
+ next unless node.at_css('.deprecated').nil?
+
+ member_name = node.at_css('.documentableName').content
+ title = "#{name}.#{member_name}"
+
+ # Add () to methods that take parameters, i.e. methods who have (…)
+ # in their signature, ignoring occurrences of (implicit …) and (using …)
+ signature = node.at_css('.signature').content
+ title += '()' if signature =~ /\((?!implicit)(?!using ).*\)/
+
+ next if titles.include?(title) # Ignore duplicates (function overloading)
+
+ entries << [title, id]
+ titles.push(title)
+ end
+
+ entries
+ end
+
+ private
+
+ # For the package name, we use the slug rather than parsing the package
+ # name from the HTML because companion object classes may be broken out into
+ # their own entries (by the source documentation). When that happens,
+ # we want to group these classes (like `scala.reflect.api.Annotations.Annotation`)
+ # under the package name, and not the fully-qualfied name which would
+ # include the companion object.
+ def package_name
+ name = package_drop_last(slug_parts)
+ name.empty? ? 'scala' : name
+ end
+
+ def parent_package
+ parent = package_drop_last(package_name.split('.'))
+ parent.empty? ? 'scala' : parent
+ end
+
+ def package_drop_last(parts)
+ parts[0...-1].join('.')
+ end
+
+ def slug_parts
+ slug.split('/')
+ end
+
+ def is_package?
+ !at_css('.cover-header .micon.pa').nil?
+ end
+ end
+ end
+end
diff --git a/lib/docs/scrapers/scala.rb b/lib/docs/scrapers/scala.rb
index dc268960..dcf28b63 100644
--- a/lib/docs/scrapers/scala.rb
+++ b/lib/docs/scrapers/scala.rb
@@ -3,24 +3,50 @@ module Docs
self.name = 'Scala'
self.type = 'scala'
self.links = {
- home: 'http://www.scala-lang.org/',
+ home: 'https://www.scala-lang.org/',
code: 'https://github.com/scala/scala'
}
- options[:container] = '#content-container'
options[:attribution] = <<-HTML
- © 2002-2019 EPFL, with contributions from Lightbend.
+ © 2002-2022 EPFL, with contributions from Lightbend.
Licensed under the Apache License, Version 2.0.
HTML
+ # For Scala 3, there is no official download link for the documentation
+ # (see https://contributors.scala-lang.org/t/5537).
+ #
+ # We currently need to build the docs ourselves. To do so:
+ # 1. Make sure that Scala 3 and sbt are installed
+ # (https://www.scala-lang.org/download/scala3.html)
+ # 2. Clone the Scala 3 (Dotty) repository (https://github.com/lampepfl/dotty)
+ # 3. From the Dotty folder, run this command in the terminal:
+ # $ sbt scaladoc/generateScalaDocumentation
+ # 4. Extract scaladoc/output/scala3/api/ into docs/scala~3.1
+ version '3.1' do
+ self.release = '3.1.1'
+ self.base_url = 'https://scala-lang.org/api/3.1.1/'
+ self.root_path = 'index.html'
+
+ options[:skip_patterns] = [
+ # Ignore class names with include “#”, which cause issues with the scraper
+ /%23/,
+
+ # Ignore local links to the Java documentation created by a Scaladoc bug
+ /java\/lang/,
+ ]
+
+ html_filters.push 'scala/entries_v3', 'scala/clean_html_v3'
+ end
+
# https://downloads.lightbend.com/scala/2.13.0/scala-docs-2.13.0.zip
# Extract api/scala-library into docs/scala~2.13_library
version '2.13 Library' do
self.release = '2.13.0'
self.base_url = 'https://www.scala-lang.org/api/2.13.0/'
self.root_path = 'index.html'
+ options[:container] = '#content-container'
- html_filters.push 'scala/entries', 'scala/clean_html'
+ html_filters.push 'scala/entries_v2', 'scala/clean_html_v2'
end
# https://downloads.lightbend.com/scala/2.13.0/scala-docs-2.13.0.zip
@@ -29,8 +55,9 @@ module Docs
self.release = '2.13.0'
self.base_url = 'https://www.scala-lang.org/api/2.13.0/scala-reflect/'
self.root_path = 'index.html'
+ options[:container] = '#content-container'
- html_filters.push 'scala/entries', 'scala/clean_html'
+ html_filters.push 'scala/entries_v2', 'scala/clean_html_v2'
end
# https://downloads.lightbend.com/scala/2.12.9/scala-docs-2.12.9.zip
@@ -39,8 +66,9 @@ module Docs
self.release = '2.12.9'
self.base_url = 'https://www.scala-lang.org/api/2.12.9/'
self.root_path = 'index.html'
+ options[:container] = '#content-container'
- html_filters.push 'scala/entries', 'scala/clean_html'
+ html_filters.push 'scala/entries_v2', 'scala/clean_html_v2'
end
# https://downloads.lightbend.com/scala/2.12.9/scala-docs-2.12.9.zip
@@ -49,13 +77,14 @@ module Docs
self.release = '2.12.9'
self.base_url = 'https://www.scala-lang.org/api/2.12.9/scala-reflect/'
self.root_path = 'index.html'
+ options[:container] = '#content-container'
- html_filters.push 'scala/entries', 'scala/clean_html'
+ html_filters.push 'scala/entries_v2', 'scala/clean_html_v2'
end
def get_latest_version(opts)
- doc = fetch_doc('https://www.scala-lang.org/api/current/', opts)
- doc.at_css('#doc-version').content
+ doc = fetch_doc('https://www.scala-lang.org/api/3.x/', opts)
+ doc.at_css('.projectVersion').content
end
end
end