diff --git a/assets/images/icons.png b/assets/images/icons.png
index 20187782..2b84306a 100644
Binary files a/assets/images/icons.png and b/assets/images/icons.png differ
diff --git a/assets/images/icons@2x.png b/assets/images/icons@2x.png
index 86f9eb42..555fb5b4 100644
Binary files a/assets/images/icons@2x.png and b/assets/images/icons@2x.png differ
diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee
index df890842..33d1e1c4 100644
--- a/assets/javascripts/templates/pages/about_tmpl.coffee
+++ b/assets/javascripts/templates/pages/about_tmpl.coffee
@@ -143,6 +143,11 @@ credits = [
'1997-2013 The PHP Documentation Group',
'CC BY',
'http://creativecommons.org/licenses/by/3.0/'
+ ], [
+ 'PostgreSQL',
+ '1996-2013 The PostgreSQL Global Development Group
© 1994 The Regents of the University of California',
+ 'PostgreSQL',
+ 'http://www.postgresql.org/about/licence/'
], [
'Python',
'1990-2013 Python Software Foundation
Python is a trademark of the Python Software Foundation.',
diff --git a/assets/javascripts/templates/pages/news_tmpl.coffee b/assets/javascripts/templates/pages/news_tmpl.coffee
index 90266907..5f6b2fb5 100644
--- a/assets/javascripts/templates/pages/news_tmpl.coffee
+++ b/assets/javascripts/templates/pages/news_tmpl.coffee
@@ -24,7 +24,10 @@ newsItem = (date, news) ->
result
app.news = [
- [ 1386892800000, # December 13, 2013
+ [ 1386979200000, # December 14, 2013
+ """ New PostgreSQL documentation """
+ ], [
+ 1386892800000, # December 13, 2013
""" New Git and Redis documentations """
], [
1385424000000, # November 26, 2013
diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss
index 4a9ed2ee..e88e3890 100644
--- a/assets/stylesheets/application.css.scss
+++ b/assets/stylesheets/application.css.scss
@@ -37,6 +37,7 @@
'pages/mdn',
'pages/node',
'pages/php',
+ 'pages/postgres',
'pages/rdoc',
'pages/redis',
'pages/rfc',
diff --git a/assets/stylesheets/global/_icons.scss b/assets/stylesheets/global/_icons.scss
index 3c8f7bb1..897d5224 100644
--- a/assets/stylesheets/global/_icons.scss
+++ b/assets/stylesheets/global/_icons.scss
@@ -44,3 +44,4 @@
._icon-python:before { background-position: 0 -6rem; }
._icon-git:before { background-position: -1rem -6rem; }
._icon-redis:before { background-position: -2rem -6rem; }
+._icon-postgresql:before { background-position: -3rem -6rem; }
diff --git a/assets/stylesheets/pages/_postgres.scss b/assets/stylesheets/pages/_postgres.scss
new file mode 100644
index 00000000..da8dc08a
--- /dev/null
+++ b/assets/stylesheets/pages/_postgres.scss
@@ -0,0 +1,13 @@
+._postgres {
+ padding-left: 1rem;
+
+ h1, h1 ~ p, h2 { margin-left: -1rem; }
+ h2 { @extend %block-heading; }
+
+ .VARIABLELIST dt { @extend %block-label, %label-blue; }
+
+ blockquote.NOTE, blockquote.IMPORTANT { @extend %note; }
+ blockquote.TIP { @extend %note, %note-green; }
+
+ p > code { @extend %label; }
+}
diff --git a/lib/docs/filters/postgresql/clean_html.rb b/lib/docs/filters/postgresql/clean_html.rb
new file mode 100644
index 00000000..8c9a9f45
--- /dev/null
+++ b/lib/docs/filters/postgresql/clean_html.rb
@@ -0,0 +1,44 @@
+module Docs
+ class Postgresql
+ class CleanHtmlFilter < Filter
+ def call
+ root_page? ? root : other
+ doc
+ end
+
+ def root
+ doc.inner_html = ' '
+ end
+
+ def other
+ css('a[name]').each do |node|
+ node.parent['id'] = node['name']
+ node.before(node.children).remove
+ end
+
+ css('div.SECT1', 'pre > kbd', 'tt > code', 'h1 > tt').each do |node|
+ node.before(node.children).remove
+ end
+
+ css('table').each do |node|
+ node.remove_attribute 'border'
+ node.remove_attribute 'width'
+ end
+
+ css('td').each do |node|
+ node.remove_attribute 'align'
+ node.remove_attribute 'valign'
+ end
+
+ css('tt').each do |node|
+ node.name = 'code'
+ end
+
+ css('.REFSYNOPSISDIV > p').each do |node|
+ node.name = 'pre'
+ node.content = node.content
+ end
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/postgresql/clean_nav.rb b/lib/docs/filters/postgresql/clean_nav.rb
new file mode 100644
index 00000000..0f6c7090
--- /dev/null
+++ b/lib/docs/filters/postgresql/clean_nav.rb
@@ -0,0 +1,25 @@
+module Docs
+ class Postgresql
+ class CleanNavFilter < Filter
+ def call
+ extract_up_path
+ extract_chapter
+ css('.NAVHEADER', '.NAVFOOTER').remove
+ doc
+ end
+
+ def extract_up_path
+ if node = at_css('.NAVHEADER a[accesskey="U"]')
+ result[:pg_up_path] = node['href']
+ end
+ end
+
+ def extract_chapter
+ return unless text = at_css('.NAVHEADER td[align="center"]').content
+ return unless match = text.match(/\AChapter (\d+)\. (.+)\z/)
+ result[:pg_chapter] = match[1].to_i
+ result[:pg_chapter_name] = match[2]
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/postgresql/entries.rb b/lib/docs/filters/postgresql/entries.rb
new file mode 100644
index 00000000..1436e5d4
--- /dev/null
+++ b/lib/docs/filters/postgresql/entries.rb
@@ -0,0 +1,153 @@
+module Docs
+ class Postgresql
+ class EntriesFilter < Docs::EntriesFilter
+ REPLACE_NAMES = {
+ 'Sorting Rows' => 'ORDER BY',
+ 'Select Lists' => 'SELECT Lists',
+ 'Data Type Formatting Functions' => 'Formatting Functions',
+ 'Enum Support Functions' => 'Enum Functions',
+ 'Row and Array Comparisons' => 'Array Comparisons',
+ 'Sequence Manipulation Functions' => 'Sequence Functions',
+ 'System Administration Functions' => 'Administration Functions',
+ 'System Information Functions' => 'Information Functions' }
+
+ def get_name
+ name = at_css('h1').content
+ clean_heading_name(name)
+
+ if %w(Overview Introduction).include?(name)
+ result[:pg_chapter_name]
+ else
+ name.sub! ' (Common Table Expressions)', ''
+ REPLACE_NAMES[name] || name
+ end
+ end
+
+ def clean_heading_name(name)
+ name.sub! %r{\A[\d\.\s]+}, ''
+ name.sub! 'Using ', ''
+ name.sub! %r{\AThe }, ''
+ name
+ end
+
+ def get_type
+ return if initial_page?
+
+ if result[:pg_up_path] == 'sql-commands.html'
+ 'Commands'
+ elsif result[:pg_up_path].start_with? 'reference-'
+ 'Applications'
+ elsif type = result[:pg_chapter_name]
+ if type.start_with?('Func') && (match = name.match(/\A(?!Form|Seq|Set|Enum)(.+) Func/))
+ "Functions: #{match[1]}"
+ else
+ type.sub 'SQL ', ''
+ end
+ end
+ end
+
+ def additional_entries
+ return [] if skip_additional_entries?
+ return get_config_entries if config_page?
+ return get_heading_entries('h3[id]') if slug == 'functions-xml'
+
+ if type == 'Data Types'
+ return get_custom_entries case slug
+ when 'rangetypes' then 'li > p > .TYPE:first-child'
+ when 'datatype-textsearch' then '.SECT2 > .TYPE'
+ else '.CALSTABLE td:first-child > .TYPE' end
+ end
+
+ entries = get_heading_entries('h2[id]')
+
+ if slug == 'queries-union'
+ entries.concat get_custom_entries('p > .LITERAL:first-child')
+ elsif slug == 'queries-table-expressions'
+ entries.concat get_heading_entries('h3[id]')
+ entries.concat get_custom_entries('dt > .LITERAL:first-child')
+ elsif slug == 'functions-logical'
+ entries.concat get_custom_entries('> table td:first-child > code')
+ elsif slug == 'functions-formatting'
+ entries.concat get_custom_entries('#FUNCTIONS-FORMATTING-TABLE td:first-child > code')
+ elsif slug == 'functions-admin'
+ entries.concat get_custom_entries('.TABLE td:first-child > code')
+ elsif slug == 'functions-string'
+ entries.concat get_custom_entries('> div[id^="FUNC"] td:first-child > code')
+ elsif type && type.start_with?('Functions')
+ entries.concat get_custom_entries('> .TABLE td:first-child > code:first-child')
+ entries.concat get_comparison_entries if slug == 'functions-comparison'
+ end
+
+ entries
+ end
+
+ def get_config_entries
+ css('.VARIABLELIST dt[id]').map do |node|
+ name = node.at_css('.VARNAME').content
+ ["Config: #{name}", node['id']]
+ end
+ end
+
+ def get_heading_entries(selector)
+ css(selector).inject [] do |entries, node|
+ name = node.content
+ clean_heading_name(name)
+
+ unless skip_heading?(name)
+ entries << ["#{additional_entry_prefix}: #{name}", node['id']]
+ end
+
+ entries
+ end
+ end
+
+ def get_custom_entries(selector)
+ css(selector).inject [] do |entries, node|
+ name = node.content
+ name.gsub! %r{\(.*?\)}m, ''
+ name.gsub! %r{\[.*?\]}m, ''
+ name.squeeze! ' '
+ name.sub! %r{\([^\)]*\z}, '' # bug fix: json_populate_record
+ name = '||' if name.include? ' || '
+ id = name.gsub(/[^a-z0-9\-_]/) { |char| char.ord }
+ id = id.parameterize
+ name.prepend "#{additional_entry_prefix}: "
+
+ unless entries.any? { |entry| entry[0] == name }
+ node['id'] = id
+ entries << [name, id]
+ end
+
+ entries
+ end
+ end
+
+ def get_comparison_entries
+ %w(IS NULL BETWEEN DISTINCT\ FROM).map do |name|
+ ["#{self.name}: #{name}"]
+ end
+ end
+
+ def additional_entry_prefix
+ type.dup.sub!('Functions: ', '') || self.name
+ end
+
+ def skip_additional_entries?
+ slug == 'config-setting' || %w(Concurrency\ Control Localization).include?(type)
+ end
+
+ def skip_heading?(name)
+ %w(Usage\ Patterns Portability Caveats Overview).include?(name) ||
+ (type.start_with?('Functions') && slug != 'functions-xml' && name.split.first.upcase!)
+ end
+
+ def include_default_entry?
+ !(initial_page? || at_css('.TOC') || config_page?)
+ end
+
+ def config_page?
+ slug.start_with? 'runtime-config'
+ end
+ end
+ end
+end
diff --git a/lib/docs/scrapers/postgresql.rb b/lib/docs/scrapers/postgresql.rb
new file mode 100644
index 00000000..909036fd
--- /dev/null
+++ b/lib/docs/scrapers/postgresql.rb
@@ -0,0 +1,55 @@
+module Docs
+ class Postgresql < FileScraper
+ self.name = 'PostgreSQL'
+ self.type = 'postgres'
+ self.version = 'up to 9.3.2'
+ self.dir = '/Users/Thibaut/DevDocs/Docs/PostgreSQL'
+ self.base_url = 'http://www.postgresql.org/docs/9.3/static/'
+ self.root_path = 'reference.html'
+ self.initial_paths = %w(sql.html runtime-config.html charset.html)
+
+ html_filters.insert_before 'normalize_urls', 'postgresql/clean_nav'
+ html_filters.push 'postgresql/clean_html', 'postgresql/entries', 'title'
+
+ options[:title] = false
+ options[:root_title] = 'PostgreSQL'
+ options[:follow_links] = ->(filter) { filter.initial_page? }
+
+ options[:only] = %w(
+ arrays.html
+ rowtypes.html
+ rangetypes.html
+ mvcc-intro.html
+ transaction-iso.html
+ explicit-locking.html
+ applevel-consistency.html
+ locking-indexes.html
+ config-setting.html
+ locale.html
+ collation.html
+ multibyte.html)
+
+ options[:only_patterns] = [
+ /\Asql\-/,
+ /\Aapp\-/,
+ /\Addl\-/,
+ /\Adml\-/,
+ /\Aqueries\-/,
+ /\Adatatype\-/,
+ /\Afunctions\-/,
+ /\Aindexes\-/,
+ /\Aruntime\-config\-/]
+
+ options[:skip] = %w(
+ ddl-others.html
+ runtime-config-custom.html
+ runtime-config-short.html
+ functions-event-triggers.html
+ functions-trigger.html)
+
+ options[:attribution] = <<-HTML
+ © 1996–2013 The PostgreSQL Global Development Group
+ Licensed under the PostgreSQL License.
+ HTML
+ end
+end
diff --git a/public/icons/docs/postgresql/16.png b/public/icons/docs/postgresql/16.png
new file mode 100644
index 00000000..22f64d43
Binary files /dev/null and b/public/icons/docs/postgresql/16.png differ
diff --git a/public/icons/docs/postgresql/16@2x.png b/public/icons/docs/postgresql/16@2x.png
new file mode 100644
index 00000000..0496ae43
Binary files /dev/null and b/public/icons/docs/postgresql/16@2x.png differ
diff --git a/public/icons/docs/postgresql/SOURCE b/public/icons/docs/postgresql/SOURCE
new file mode 100644
index 00000000..84ce600b
--- /dev/null
+++ b/public/icons/docs/postgresql/SOURCE
@@ -0,0 +1 @@
+http://www.postgresql.org/about/press/presskit93/#logos