diff --git a/assets/javascripts/news.json b/assets/javascripts/news.json
index 68188b42..12d6569b 100644
--- a/assets/javascripts/news.json
+++ b/assets/javascripts/news.json
@@ -1,4 +1,8 @@
[
+ [
+ "2020-12-23",
+ "New documentation: GTK"
+ ],
[
"2020-12-07",
"New documentations: Flask, Groovy, Jinja, Werkzeug"
diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee
index 4405aa26..217bb32b 100644
--- a/assets/javascripts/templates/pages/about_tmpl.coffee
+++ b/assets/javascripts/templates/pages/about_tmpl.coffee
@@ -356,6 +356,11 @@ credits = [
'GruntJS Team',
'MIT',
'https://github.com/gruntjs/grunt-docs/blob/master/package.json#L10'
+ ], [
+ 'GTK',
+ 'The GNOME Project',
+ 'LGPLv2.1+',
+ 'https://gitlab.gnome.org/GNOME/gtk/-/blob/master/COPYING'
], [
'Handlebars',
'2011-2017 Yehuda Katz',
diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss
index 97347da6..fed660dc 100644
--- a/assets/stylesheets/application.css.scss
+++ b/assets/stylesheets/application.css.scss
@@ -59,10 +59,11 @@
'pages/express',
'pages/git',
'pages/github',
+ 'pages/gnuplot',
'pages/go',
'pages/graphite',
'pages/groovy',
- 'pages/gnuplot',
+ 'pages/gtk',
'pages/haproxy',
'pages/haskell',
'pages/jekyll',
diff --git a/assets/stylesheets/pages/_gtk.scss b/assets/stylesheets/pages/_gtk.scss
new file mode 100644
index 00000000..9df0cd3e
--- /dev/null
+++ b/assets/stylesheets/pages/_gtk.scss
@@ -0,0 +1,15 @@
+._gtk {
+ padding-left: 1rem;
+
+ h1, h2, h3 { margin-left: -1rem; }
+ h2 { @extend %block-heading; }
+ h3 { @extend %block-label, %label-blue; }
+
+ div.toc { margin-top: 1.5em; }
+ .toc dl { margin-top: 0; margin-bottom: 0; }
+
+ .note { @extend %note, %note-green; }
+ .warning { @extend %note, %note-orange; }
+
+ .gallery-float { float:left; }
+}
diff --git a/docs/scraper-reference.md b/docs/scraper-reference.md
index c8ad24a4..fc00876d 100644
--- a/docs/scraper-reference.md
+++ b/docs/scraper-reference.md
@@ -190,6 +190,8 @@ More information about how filters work is available on the [Filter Reference](.
In order to keep scrapers up-to-date the `get_latest_version(opts)` method should be overridden. If `self.release` is defined, this should return the latest version of the documentation. If `self.release` is not defined, it should return the Epoch time when the documentation was last modified. If the documentation will never change, simply return `1.0.0`. The result of this method is periodically reported in a "Documentation versions report" issue which helps maintainers keep track of outdated documentations.
To make life easier, there are a few utility methods that you can use in `get_latest_version`:
+
+### General HTTP methods
* `fetch(url, opts)`
Makes a GET request to the url and returns the response body.
@@ -205,11 +207,15 @@ To make life easier, there are a few utility methods that you can use in `get_la
Makes a GET request to the url and returns the JSON body converted to a dictionary.
Example: [lib/docs/scrapers/mdn/mdn.rb](../lib/docs/scrapers/mdn/mdn.rb)
+
+### Package repository methods
* `get_npm_version(package, opts)`
Returns the latest version of the given npm package.
Example: [lib/docs/scrapers/bower.rb](../lib/docs/scrapers/bower.rb)
+
+### GitHub methods
* `get_latest_github_release(owner, repo, opts)`
Returns the tag name of the latest GitHub release of the given repository. If the tag name is preceded by a "v", the "v" will be removed.
@@ -225,3 +231,15 @@ To make life easier, there are a few utility methods that you can use in `get_la
Returns the contents of the requested file in the default branch of the given repository.
Example: [lib/docs/scrapers/minitest.rb](../lib/docs/scrapers/minitest.rb)
+* `get_latest_github_commit_date(owner, repo, opts)`
+
+ Returns the date of the most recent commit in the default branch of the given repository.
+
+ Example: [lib/docs/scrapers/reactivex.rb](../lib/docs/scrapers/reactivex.rb)
+
+### GitLab methods
+* `get_gitlab_tags(hostname, group, project, opts)`
+
+ Returns the list of tags on the given repository ([format](https://docs.gitlab.com/ee/api/tags.html)).
+
+ Example: [lib/docs/scrapers/gtk.rb](../lib/docs/scrapers/gtk.rb)
diff --git a/lib/docs/core/doc.rb b/lib/docs/core/doc.rb
index 3b67acb1..979e2c41 100644
--- a/lib/docs/core/doc.rb
+++ b/lib/docs/core/doc.rb
@@ -270,5 +270,9 @@ module Docs
timestamp = commits[0]['commit']['author']['date']
Date.iso8601(timestamp).to_time.to_i
end
+
+ def get_gitlab_tags(hostname, group, project, opts)
+ fetch_json("https://#{hostname}/api/v4/projects/#{group}%2F#{project}/repository/tags", opts)
+ end
end
end
diff --git a/lib/docs/filters/gtk/clean_html.rb b/lib/docs/filters/gtk/clean_html.rb
new file mode 100644
index 00000000..d093ef47
--- /dev/null
+++ b/lib/docs/filters/gtk/clean_html.rb
@@ -0,0 +1,69 @@
+module Docs
+ class Gtk
+ class CleanHtmlFilter < Filter
+ def call
+
+ css('table#top', 'table > colgroup', 'h2.title', '.footer', 'hr', 'br').remove
+
+ if root_page?
+ css('span > a').each do |node|
+ node.parent.before(node).remove
+ end
+ end
+
+ if top_table = at_css('.refnamediv > table')
+ top_table.css('td > p', 'td > img').each do |node|
+ top_table.before(node)
+ end
+ top_table.remove
+ end
+
+ css('table').each do |node|
+ node.remove_attribute 'border'
+ node.remove_attribute 'width'
+ end
+
+ # Move anchors to general headings
+ css('h2 > a[name]', 'h3 > a[name]').each do |node|
+ node.parent['id'] = node['name']
+ end
+
+ # Move anchors to function/struct/enum/etc.
+ css('a[name] + h2', 'a[name] + h3').each do |node|
+ node['id'] = node.previous_element['name']
+ end
+
+ # Move anchors to struct and enum members
+ css('td.struct_member_name', 'td.enum_member_name').each do |node|
+ node['id'] = node.at_css('a[name]')['name']
+ end
+
+ # Remove surrounding table from code blocks
+ css('.listing_frame').each do |node|
+ node.before(at_css('.listing_code')).remove
+ end
+
+ css('.literallayout code').each do |node|
+ node.name = 'pre'
+ end
+
+ # Fix code highlighting
+ css('pre').each do |node|
+ # If a codeblock has URLs, don't touch it
+ next if node.at_css('a[href]')
+
+ node.content = node.content
+
+ # it's not perfect, but make a guess at language
+ if node.content =~ /\<\?xml/
+ node['data-language'] = 'xml'
+ else
+ node['data-language'] = 'c'
+ end
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/gtk/entries.rb b/lib/docs/filters/gtk/entries.rb
new file mode 100644
index 00000000..5cb3d27a
--- /dev/null
+++ b/lib/docs/filters/gtk/entries.rb
@@ -0,0 +1,154 @@
+module Docs
+ class Gtk
+ class EntriesFilter < Docs::EntriesFilter
+ # The GTK documentation paths are "flat" and while the contents of each
+ # page provides a way to determine the direct parent relationship, we
+ # really need a full hierarchy of pages *a priori* to be able to fully
+ # categorize all pages and entries. So we're going to recursivly generate
+ # a full map of page -> parent relationships from the table of contents...
+ PARENT_BY_PATH = {}
+
+ # And then use it to generate the list of 'breadcrumb' paths for each page
+ # ex: ['gtkwidgets', 'fancywidgets', 'gtkexamplewidget']
+ attr_accessor :breadcrumbs
+
+ # Map of paths to Page Names/Titles
+ NAME_BY_PATH = {}
+
+ # GTK+ 3 and GTK 4
+ REPLACE_NAMES = {
+ 'I. GTK+ Overview' => '1. Overview',
+ 'II. GTK+ Widgets and Objects' => '2. Widgets and Objects',
+ 'III. GTK+ Core Reference' => '3. Core Reference',
+ 'IV. Theming in GTK+' => '4. Theming',
+ 'VI. GTK+ Tools' => '6. Tools',
+ 'VII. GTK+ Platform Support' => '7. Platform Support',
+
+ 'I. Introduction' => '1. Introduction',
+ 'II. GTK Concepts' => '2. Concepts',
+ 'III. GTK Widgets and Objects' => '3. Widgets and Objects',
+ 'IV. GTK Core Reference' => '4. Core Reference',
+ 'V. Theming in GTK' => '5. Theming',
+ 'VII. GTK Tools' => '7. Tools',
+ 'VIII. GTK Platform Support' => '8. Platform Support'
+ }
+
+ def call
+ if root_page?
+ # Generate NAME_BY_PATH Hash
+ css('dl.toc a').each do |node|
+ name = node.content
+ path = node['href'].split('/').last.remove('.html')
+ NAME_BY_PATH[path] = REPLACE_NAMES[name] || name
+ end
+ # Generate PARENT_BY_PATH Hash
+ process_toc(at_css('dl.toc'), nil)
+ end
+ super
+ end
+
+ # Recursive depth-first search
+ # Treat solo 'dt' nodes as leaf nodes and
+ # sibling 'dt + dd' nodes as nodes with children
+ def process_toc(toc_dl_node, parent_path)
+ toc_dl_node.css('> dt').each do |node|
+ node_path = node.at_css('a')['href'].split('/').last.remove('.html')
+ PARENT_BY_PATH[node_path] = parent_path
+
+ if node.next_element && node.next_element.name == 'dd'
+ children = node.next_element.children.first
+ process_toc(children, node_path)
+ end
+ end
+ end
+
+ def breadcrumbs
+ @breadcrumbs ||= get_breadcrumbs
+ end
+
+ def get_breadcrumbs
+ return [] if root_page?
+
+ breadcrumbs = []
+ path = slug.downcase
+ while path
+ breadcrumbs.prepend path
+ path = PARENT_BY_PATH[path]
+ end
+
+ breadcrumbs
+ end
+
+ def get_name
+ NAME_BY_PATH[self.breadcrumbs.last]
+ end
+
+ def get_type
+ NAME_BY_PATH[self.breadcrumbs.first]
+ end
+
+ def additional_entries
+ entries = []
+ type = case self.breadcrumbs.first
+ when 'gtkobjects'
+ "Widgets / #{NAME_BY_PATH[self.breadcrumbs[1]]}"
+ when 'gtkbase'
+ "Base / #{self.name}"
+ when'theming'
+ "Theming / #{self.name}"
+ end
+
+ if funcs = at_css('h2:contains("Functions")')
+ funcs.next_element.css('td.function_name > a').each do |node|
+ name = "#{node.content}()"
+ id = node['href']
+ entries << [name, id, type]
+ end
+ end
+
+ css('td.property_name > a').each do |node|
+ name = "#{node.content} (#{self.name} property)"
+ id = node['href']
+ entries << [name, id, type]
+ end
+
+ css('td.signal_name > a').each do |node|
+ name = "#{node.content} (#{self.name} signal)"
+ id = node['href']
+ entries << [name, id, type]
+ end
+
+ css('td.enum_member_name').each do |node|
+ name = node.content
+ id = node.at_css('a')['name']
+ entries << [name, id, type]
+ end
+
+ if values_node = at_css('h2:contains("Types and Values") + .informaltable')
+ values_node.css('tr').each do |node|
+ data_type = node.css('td').first.content
+ name = node.at_css('td.function_name').content
+ if data_type == ' '
+ name = "#{name} (type)"
+ elsif data_type == 'enum' || data_type == 'struct'
+ name = "#{name} (#{data_type})"
+ end
+ id = node.at_css('td.function_name a')['href']
+ entries << [name, id, type]
+ end
+ end
+
+ if slug == 'gtk-running'
+ css('h3 > a[name]').each do |node|
+ name = node.parent.content
+ id = node['name']
+ entries << [name, id, 'Platform / Environment Variables']
+ end
+ end
+
+ entries
+ end
+
+ end
+ end
+end
diff --git a/lib/docs/scrapers/gtk.rb b/lib/docs/scrapers/gtk.rb
new file mode 100644
index 00000000..cfa85956
--- /dev/null
+++ b/lib/docs/scrapers/gtk.rb
@@ -0,0 +1,116 @@
+module Docs
+ class Gtk < UrlScraper
+ self.name = 'GTK'
+ self.slug = 'gtk'
+ self.type = 'gtk'
+ self.root_path = 'index.html'
+ self.links = {
+ home: 'https://www.gtk.org/',
+ code: 'https://gitlab.gnome.org/GNOME/gtk/'
+ }
+
+ html_filters.push 'gtk/entries', 'gtk/clean_html', 'title'
+
+ options[:container] = '.content'
+
+ # These are all "index"-ish pages with no valuable content
+ GTK3_SKIP = %w(
+ gtk.html
+ gtk-resources.html gtk-question-index.html
+ gtkobjects.html
+ Application.html Builder.html WindowWidgets.html LayoutContainers.html
+ DisplayWidgets.html ButtonWidgets.html NumericEntry.html
+ TextWidgetObjects.html TreeWidgetObjects.html MenusAndCombos.html
+ SelectorWidgets.html Ornaments.html ScrollingWidgets.html Printing.html
+ ShortcutsOverview.html MiscObjects.html AbstractObjects.html
+ PlugSocket.html RecentDocuments.html ApplicationChoosing.html
+ Gestures.html DeprecatedObjects.html
+ gtkbase.html
+ theming.html
+ migrating.html
+ ch26s02.html ch28s02.html
+ pt06.html
+ platform-support.html
+ glossary.html
+ annotation-glossary.html
+ )
+
+ GTK3_SKIP_PATTERNS = [
+ /migrating/, /checklist/, /ch30/, /ch32/, /api-index-/
+ ]
+
+ # These are all "index"-ish pages with no valuable content
+ GTK4_SKIP = %w(
+ gtk.html
+ gtk-resources.html gtk-question-index.html ch02s02.html
+ concepts.html
+ gtkobjects.html
+ Lists.html Trees.html Application.html Builder.html WindowWidgets.html
+ LayoutContainers.html LayoutManagers.html DisplayWidgets.html
+ MediaSupport.html ButtonWidgets.html NumericEntry.html
+ MenusAndCombos.html SelectorWidgets.html DrawingWidgets.html
+ Ornaments.html ScrollingWidgets.html Printing.html
+ ShortcutsOverview.html MiscObjects.html AbstractObjects.html
+ RecentDocuments.html ApplicationChoosing.html Gestures.html ch36.html
+ ch37.html
+ gtkbase.html
+ theming.html
+ migrating.html
+ ch41s02.html ch41s03.html
+ pt07.html
+ platform-support.html
+ api-index-full.html
+ annotation-glossary.html
+ )
+
+ GTK4_SKIP_PATTERNS = [
+ /migrating/, /ch03/, /ch09/, /ch10/
+ ]
+
+ options[:attribution] = <<-HTML
+ © 2005–2020 The GNOME Project
+ Licensed under the GNU Lesser General Public License version 2.1 or later.
+ HTML
+
+ version '4.0' do
+ self.release = '4.0.0'
+ self.base_url = "https://developer.gnome.org/gtk4/#{self.version}/"
+
+ options[:root_title] = 'GTK 4 Reference Manual'
+ options[:skip] = GTK4_SKIP
+ options[:skip_patterns] = GTK4_SKIP_PATTERNS
+ end
+
+ version '3.24' do
+ self.release = '3.24.24'
+ self.base_url = "https://developer.gnome.org/gtk3/#{self.version}/"
+
+ options[:root_title] = 'GTK+ 3 Reference Manual'
+ options[:skip] = GTK3_SKIP
+ options[:skip_patterns] = GTK3_SKIP_PATTERNS
+ end
+
+ version '3.22' do
+ self.release = '3.22.3'
+ self.base_url = "https://developer.gnome.org/gtk3/#{self.version}/"
+
+ options[:root_title] = 'GTK+ 3 Reference Manual'
+ options[:skip] = GTK3_SKIP
+ options[:skip_patterns] = GTK3_SKIP_PATTERNS
+ end
+
+ version '3.20' do
+ self.release = '3.20.4'
+ self.base_url = "https://developer.gnome.org/gtk3/#{self.version}/"
+
+ options[:root_title] = 'GTK+ 3 Reference Manual'
+ options[:skip] = GTK3_SKIP
+ options[:skip_patterns] = GTK3_SKIP_PATTERNS
+ end
+
+ def get_latest_version(opts)
+ tags = get_gitlab_tags('gitlab.gnome.org', 'GNOME', 'gtk', opts)
+ tags[0]['name']
+ end
+ end
+end
diff --git a/public/icons/docs/gtk/16.png b/public/icons/docs/gtk/16.png
new file mode 100644
index 00000000..d3a7e72a
Binary files /dev/null and b/public/icons/docs/gtk/16.png differ
diff --git a/public/icons/docs/gtk/16@2x.png b/public/icons/docs/gtk/16@2x.png
new file mode 100644
index 00000000..dd3482d6
Binary files /dev/null and b/public/icons/docs/gtk/16@2x.png differ
diff --git a/public/icons/docs/gtk/SOURCE b/public/icons/docs/gtk/SOURCE
new file mode 100644
index 00000000..a18c5432
--- /dev/null
+++ b/public/icons/docs/gtk/SOURCE
@@ -0,0 +1 @@
+https://www.gtk.org/assets/img/logo-gtk-sm.png