diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee
index 1698eec2..fc256fd9 100644
--- a/assets/javascripts/templates/pages/about_tmpl.coffee
+++ b/assets/javascripts/templates/pages/about_tmpl.coffee
@@ -686,6 +686,11 @@ credits = [
'2012-2018 The Qt Company Ltd',
'GFDL',
'https://doc.qt.io/qt-5/licensing.html'
+ ], [
+ 'R',
+ '1999–2012 R Foundation for Statistical Computing',
+ 'GPL',
+ 'https://svn.r-project.org/R/trunk/COPYING'
], [
'Ramda',
'2013-2020 Scott Sauyet and Michael Hurley',
diff --git a/lib/docs/filters/r/clean_html.rb b/lib/docs/filters/r/clean_html.rb
new file mode 100644
index 00000000..62f9d140
--- /dev/null
+++ b/lib/docs/filters/r/clean_html.rb
@@ -0,0 +1,61 @@
+module Docs
+ class R
+ class CleanHtmlFilter < Filter
+ def call
+ slug_parts = slug.split('/')
+
+ if root_page?
+ css('a[href$="/00index"]').each do |pkg|
+ pkg['href'] = "/r-#{pkg['href'].split('/')[1]}/"
+ end
+
+ elsif slug_parts[0] == 'library'
+ title = at_css('h2')
+ title.inner_html = "#{slug_parts[3]}
#{title.content}"
+
+ summary = at_css('table[summary]')
+ summary.remove if summary
+
+ css('hr ~ *, hr').remove
+
+ elsif slug_parts[-2] == 'manual'
+ css('table.menu, div.header, hr, h2.contents-heading, div.contents, table.index-cp, table.index-vr, table[summary]').remove
+
+ css('h2').each do |node|
+ node.remove if node.content.end_with? ' index'
+ end
+
+ css('span[id] + h1, span[id] + h2, span[id] + h3, span[id] + h4, span[id] + h5, span[id] + h6').each do |node|
+ # We need the first of the series of span with ids
+ span = node.previous_element
+ while span.previous
+ prev = span.previous_element
+ break unless prev.name == 'span' and prev['id']
+ span.remove
+ span = prev
+ end
+
+ node['id'] = span['id']
+ span.remove
+
+ css('div.example').each do |node|
+ node.replace(node.children)
+ end
+ end
+
+ css('h1 + h1').remove
+
+ css('.footnote h5').each do |node|
+ anchor = node.at_css('a[id]')
+ footnote = node.next_sibling
+ footnote.inner_html = "#{anchor.text} #{footnote.inner_html}"
+ footnote['id'] = anchor['id']
+ node.remove
+ end
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/r/entries.rb b/lib/docs/filters/r/entries.rb
new file mode 100644
index 00000000..ed09345d
--- /dev/null
+++ b/lib/docs/filters/r/entries.rb
@@ -0,0 +1,84 @@
+module Docs
+ class R
+ class EntriesFilter < Docs::EntriesFilter
+
+ PKG_INDEX_ENTRIES = Hash.new []
+
+ def call
+ if slug_parts[-1] == '00Index'
+ dir = File.dirname(result[:subpath])
+ css('tr a').each do |link|
+ PKG_INDEX_ENTRIES[link['href']] += [link.text]
+ next if link['href'] == link.text
+ context[:replace_paths][File.join(dir, "#{link.text}.html")] = File.join(dir, "#{link['href']}.html")
+ end
+ end
+
+ super
+ end
+
+ def slug_parts
+ slug.split('/')
+ end
+
+ def is_package?
+ slug_parts[0] == 'library'
+ end
+
+ def is_manual?
+ slug_parts[1] == 'manual'
+ end
+
+ def get_name
+ return at_css('h2').content if is_package?
+ title = at_css('h1.settitle')
+ title ? title.content : at_css('h1, h2').content
+ end
+
+ def get_type
+ return slug_parts[1] if is_package?
+ return at_css('h1.settitle').content if is_manual?
+ end
+
+ def include_default_entry?
+ is_package? and not slug_parts[-1] == '00Index'
+ end
+
+ def manual_section(node)
+ title = node.content.sub /^((Appendix )?[A-Z]|[0-9]+)(\.[0-9]+)* /, ''
+ title unless ['References', 'Preface', 'Acknowledgements'].include?(title) or title.end_with?(' index')
+ end
+
+ def additional_entries
+ if is_package? and slug_parts[-1] != '00Index'
+ page = slug_parts[-1]
+ return [page] + PKG_INDEX_ENTRIES.fetch(page, [])
+ end
+
+ return [] unless is_manual?
+
+ entries = []
+ unless slug_parts[-1].downcase == 'r-intro'
+ # Single top-level category
+ css('div.contents > ul a').each do |link|
+ link_name = manual_section(link)
+ entries << [link_name, link['href'].split('#')[1], name] unless link_name.nil?
+ end
+ else
+ # Split 1st level of manual into different categories
+ css('div.contents > ul > li').each do |node|
+ type = manual_section(node.at_css('a'))
+ next if type.nil?
+ node.css('> ul a').each do |link|
+ link_name = link.content.sub /^[0-9A-Z]+(\.[0-9]+)* /, ''
+ entries << [link_name, link['href'].split('#')[1], type]
+ end
+ end
+ end
+ return entries
+ end
+
+ private
+ end
+ end
+end
diff --git a/lib/docs/scrapers/r.rb b/lib/docs/scrapers/r.rb
new file mode 100644
index 00000000..308d1a6b
--- /dev/null
+++ b/lib/docs/scrapers/r.rb
@@ -0,0 +1,53 @@
+module Docs
+ class R < FileScraper
+ self.name = 'R'
+ self.slug = 'r'
+ self.type = 'simple'
+ self.release = '4.1.0'
+ self.links = {
+ home: 'https://www.r-project.org/',
+ code: 'https://svn.r-project.org/R/'
+ }
+
+ self.root_path = 'doc/html/packages.html'
+
+ html_filters.push 'r/entries', 'r/clean_html'
+
+ options[:skip_links] = false
+
+ options[:attribution] = <<-HTML
+ Copyright (©) 1999–2012 R Foundation for Statistical Computing.
+ Licensed under the GNU General Public License.
+ HTML
+
+ # Never want those
+ options[:skip_patterns] = [
+ /\/DESCRIPTION$/,
+ /\/NEWS(\.[^\/]*)?$/,
+ /\/doc\/index\.html$/,
+ /\/demo$/,
+ /\.pdf$/
+ ]
+
+ options[:replace_paths] = {
+ ## We want to fix links like so − but only if the targets don’t exist:
+ # 'library/MASS/html/cov.mve.html' => 'library/MASS/html/cov.rob.html'
+ ## Paths for target packages or keywords that do not have their own file
+ ## are generated in the entries filter from 00Index.html files
+ }
+
+ options[:skip] = %w(
+ doc/html/packages-head-utf8.html
+ doc/html/SearchOn.html
+ doc/html/Search.html
+ doc/html/UserManuals.html
+ doc/html/faq.html
+ doc/manual/R-FAQ.html
+ doc/manual/R-admin.html
+ doc/manual/R-exts.html
+ doc/manual/R-ints.html
+ doc/manual/R-lang.html
+ )
+
+ end
+end
diff --git a/public/icons/docs/r/16.png b/public/icons/docs/r/16.png
new file mode 100644
index 00000000..2b0c75da
Binary files /dev/null and b/public/icons/docs/r/16.png differ
diff --git a/public/icons/docs/r/16@2x.png b/public/icons/docs/r/16@2x.png
new file mode 100644
index 00000000..51854fc2
Binary files /dev/null and b/public/icons/docs/r/16@2x.png differ
diff --git a/public/icons/docs/r/SOURCE b/public/icons/docs/r/SOURCE
new file mode 100644
index 00000000..c02c1e37
--- /dev/null
+++ b/public/icons/docs/r/SOURCE
@@ -0,0 +1 @@
+https://svn.r-project.org/R/trunk/doc/html/Rlogo.svg