diff --git a/assets/javascripts/news.json b/assets/javascripts/news.json
index 030fddec..be85cd62 100644
--- a/assets/javascripts/news.json
+++ b/assets/javascripts/news.json
@@ -1,4 +1,8 @@
[
+ [
+ "2020-12-07",
+ "New documentation: Groovy"
+ ],
[
"2020-12-04",
"New documentation: HAProxy"
diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee
index afaf37a3..473c3b41 100644
--- a/assets/javascripts/templates/pages/about_tmpl.coffee
+++ b/assets/javascripts/templates/pages/about_tmpl.coffee
@@ -341,6 +341,11 @@ credits = [
'2008-2012 Chris Davis
© 2011-2016 The Graphite Project',
'Apache',
'https://raw.githubusercontent.com/graphite-project/graphite-web/master/LICENSE'
+ ], [
+ 'Groovy',
+ '2003-2020 The Apache Software Foundation',
+ 'Apache',
+ 'https://github.com/apache/groovy-website/blob/asf-site/LICENSE'
], [
'Grunt',
'GruntJS Team',
diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss
index 175d96a6..41fa4c1b 100644
--- a/assets/stylesheets/application.css.scss
+++ b/assets/stylesheets/application.css.scss
@@ -61,6 +61,7 @@
'pages/github',
'pages/go',
'pages/graphite',
+ 'pages/groovy',
'pages/gnuplot',
'pages/haproxy',
'pages/haskell',
diff --git a/assets/stylesheets/pages/_groovy.scss b/assets/stylesheets/pages/_groovy.scss
new file mode 100755
index 00000000..d8c150d1
--- /dev/null
+++ b/assets/stylesheets/pages/_groovy.scss
@@ -0,0 +1,13 @@
+._groovy {
+ padding-left: 1rem;
+
+ h1, h2 { margin-left: -1rem; }
+ h2 { @extend %block-heading; }
+ h3 { @extend %block-label; }
+
+ .constructor { @extend %label-blue; }
+ .method { @extend %label-blue; }
+ .element { @extend %label-green; }
+ .field { @extend %label-green; }
+ .enum_constant { @extend %label-orange; }
+}
diff --git a/lib/docs/filters/groovy/clean_html.rb b/lib/docs/filters/groovy/clean_html.rb
new file mode 100755
index 00000000..baf88527
--- /dev/null
+++ b/lib/docs/filters/groovy/clean_html.rb
@@ -0,0 +1,97 @@
+module Docs
+ class Groovy
+ class CleanHtmlFilter < Filter
+ def new_node(content)
+ node = Nokogiri::XML::Node.new 'h1', doc
+ node.content = content
+ node
+ end
+
+ def call
+ title = at_css('.title').content
+ @doc = at_css('.contentContainer')
+ doc.child.before new_node(title)
+
+ if root_page?
+ css('tr > td > a').each do |node|
+ node.parent.content = node.content
+ end
+ end
+
+ css('.subNav', '.bottomNav').remove
+
+ css('hr + br', 'p + br', 'div + br', 'hr').remove
+
+ css('table').each do |node|
+ node.remove_attribute 'summary'
+ node.remove_attribute 'cellspacing'
+ node.remove_attribute 'cellpadding'
+ node.remove_attribute 'border'
+ end
+
+ # Move anchor name/id to heading tag
+ css('a[name] + h3').each do |node|
+ node['id'] = node.previous_element['name']
+ end
+
+ css('a[name] + ul.blockListLast').each do |node|
+ node.at_css('li > h4')['id'] = node.previous_element['name']
+ end
+
+ # Tag constructors, methods, and elements before removing context tags
+ css('#constructor_detail').each do |node|
+ node.parent.css('h4').each do |n|
+ n['class'] = 'constructor'
+ end
+ end
+
+ css('#method_detail').each do |node|
+ node.parent.css('h4').each do |n|
+ n['class'] = 'method'
+ end
+ end
+
+ css('#element_detail').each do |node|
+ node.parent.css('h4').each do |n|
+ n['class'] = 'element'
+ end
+ end
+
+ css('#field_detail').each do |node|
+ node.parent.css('h4').each do |n|
+ n['class'] = 'field'
+ end
+ end
+
+ css('#enum_constant_detail').each do |node|
+ node.parent.css('h4').each do |n|
+ n['class'] = 'enum_constant'
+ end
+ end
+
+ # Flatten and remove unnecessary intermediate tags
+ css('ul.blockList > li.blockList', 'ul.blockListLast > li.blockList').each do |node|
+ node.before(node.children).remove
+ end
+
+ css('ul.blockList', 'ul.blockListLast').each do |node|
+ node.before(node.children).remove
+ end
+
+ css('ul.blockList > table').each do |node|
+ node.parent.before(node).remove
+ end
+
+ css('h3', 'h4').each do |node|
+ node.name = node.name.sub(/\d/) { |i| i.to_i - 1 }
+ end
+
+ css('pre').each do |node|
+ node['data-language'] = 'groovy'
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/groovy/entries.rb b/lib/docs/filters/groovy/entries.rb
new file mode 100755
index 00000000..8f80971f
--- /dev/null
+++ b/lib/docs/filters/groovy/entries.rb
@@ -0,0 +1,28 @@
+module Docs
+ class Groovy
+ class EntriesFilter < Docs::EntriesFilter
+ def get_name
+ slug.split('/').last
+ end
+
+ def get_type
+ slug.split('/')[0..-2].join('.')
+ end
+
+ def include_default_entry?
+ slug.split('/').last != 'package-summary'
+ end
+
+ def additional_entries
+ entries = []
+ css('.method, .element, .field, .enum_constant').each do |node|
+ entries << [@name + '.' + node['id'], node['id']]
+ end
+ css('.constructor').each do |node|
+ entries << [node['id'], node['id']]
+ end
+ entries
+ end
+ end
+ end
+end
diff --git a/lib/docs/scrapers/groovy.rb b/lib/docs/scrapers/groovy.rb
new file mode 100755
index 00000000..ebd4242c
--- /dev/null
+++ b/lib/docs/scrapers/groovy.rb
@@ -0,0 +1,46 @@
+module Docs
+ class Groovy < UrlScraper
+ self.type = 'groovy'
+ self.root_path = 'overview-summary.html'
+ self.links = {
+ home: 'https://groovy-lang.org/',
+ code: 'https://github.com/apache/groovy'
+ }
+
+ html_filters.push 'groovy/clean_html', 'groovy/entries'
+
+ options[:skip] = %w(
+ index-all.html
+ deprecated-list.html
+ help-doc.html
+ )
+ options[:skip_patterns] = [
+ /\Aindex.html/
+ ]
+
+ options[:attribution] = <<-HTML
+ © 2003-2020 The Apache Software Foundation
+ Licensed under the Apache license.
+ HTML
+
+ version '3.0' do
+ self.release = '3.0.7'
+ self.base_url = "https://docs.groovy-lang.org/#{self.release}/html/gapi/"
+ end
+
+ version '2.5' do
+ self.release = '2.5.14'
+ self.base_url = "https://docs.groovy-lang.org/#{self.release}/html/gapi/"
+ end
+
+ version '2.4' do
+ self.release = '2.4.21'
+ self.base_url = "https://docs.groovy-lang.org/#{self.release}/html/gapi/"
+ end
+
+ def get_latest_version(opts)
+ doc = fetch_doc('https://groovy.apache.org/download.html', opts)
+ doc.at_css('#big-download-button').content.split(' ').last
+ end
+ end
+end
diff --git a/public/icons/docs/groovy/16.png b/public/icons/docs/groovy/16.png
new file mode 100644
index 00000000..0cb5090f
Binary files /dev/null and b/public/icons/docs/groovy/16.png differ
diff --git a/public/icons/docs/groovy/16@2x.png b/public/icons/docs/groovy/16@2x.png
new file mode 100644
index 00000000..3d205449
Binary files /dev/null and b/public/icons/docs/groovy/16@2x.png differ
diff --git a/public/icons/docs/groovy/SOURCE b/public/icons/docs/groovy/SOURCE
new file mode 100644
index 00000000..9aaa6100
--- /dev/null
+++ b/public/icons/docs/groovy/SOURCE
@@ -0,0 +1 @@
+https://docs.groovy-lang.org/latest/html/gapi/groovy.ico