added devhelp generator

pull/50/head
naquad 11 years ago
parent 4a654feb1b
commit 6d23cd241a

@ -0,0 +1,42 @@
var HL = {
'javascript': [
'._coffeescript pre:last-child',
'._angular .prettyprint',
'._d3 .highlight > pre',
'._underscore pre',
'._node pre > code',
'._jquery .syntaxhighlighter .javascript',
'._ember pre .javascript',
['._knockout pre', 'data-bind="', false],
'._mdn pre[class*="js"]'
],
'c': [ '._ruby pre.c' ],
'ruby': [ '._ruby pre.ruby' ],
'coffeescript': [ '._coffeescript .code > pre:first-child' ],
'python': [ '._sphinx pre.python' ],
'markup': [
['._knockout pre', 'data-bind="', true],
'._ember pre.html',
'._mdn pre[class*="html"]'
]
};
function highlightAll(sels, language){
for(var i = 0; i < sels.length; ++i){
var sel = sels[i] instanceof Array ? sels[i] : [sels[i]];
var nodes = document.querySelectorAll(sel[0]);
for(var j = 0, c = nodes.length; j < c; ++j){
if(!sel[1] || nodes[j].innerHTML.indexOf(sel[i][1]) != -1 == sel[2]){
nodes[j].classList.add('language-' + language)
Prism.highlightElement(nodes[j]);
}
}
}
}
for(var lang in HL)
if(HL.hasOwnProperty(lang))
highlightAll(HL[lang], lang);

@ -14,7 +14,8 @@
@import 'global/variables', @import 'global/variables',
'global/icons', 'global/icons',
'global/classes', 'global/classes',
'global/base'; 'global/base',
'global/devhelp';
@import 'components/app', @import 'components/app',
'components/header', 'components/header',

@ -0,0 +1,22 @@
._devhelp {
width: 90%;
height: auto !important;
margin: 0 auto !important;
border: none !important;
box-shadow: none !important;
font-family: Arial, sans-serif;
pre, code, samp, ._redis > .example {
font-family: "Lucida Console", "Sans Mono", "Courier New", monospace;
font-size: 1.05em;
}
._content {
height: auto !important;
margin-left: 0px !important;
overflow-y: visible !important;
font-size: 1.1em;
}
}

@ -0,0 +1,218 @@
require 'find'
require 'fileutils'
require 'nokogiri'
require 'json'
require 'downloader'
class DevHelp
SKIP_ASSETS = %{.json .js .gz}
DEVHELP_NS = 'http://www.devhelp.net/book'
def initialize(options)
@options = options
end
def book_options(doc)
{
xmlns: DEVHELP_NS,
title: doc.name,
name: "#{doc.slug}-#{doc.version}",
version: 2,
author: '',
language: doc.language,
link: 'index.html'
}
end
def normalize_url(link)
link.gsub(/^([^.]+?)(?=$|#)/, '\1.html\2')
end
def build_devhelp(doc, structure)
builder do |xml|
xml.book book_options(doc) do
xml.doc.create_internal_subset('book', '-//W3C//DTD HTML 4.01 Transitional//EN', '')
xml.chapters do
structure[:terms].each do |term, link|
xml.sub name: term, link: normalize_url(link)
end
end
end
end.to_xml
end
def for_docs(*docs)
docs.flatten.each(&method(:for_doc))
end
def cp_r(src, dst)
t = File.dirname(dst)
FileUtils.mkdir_p(t) unless File.directory?(t)
FileUtils.cp_r(src, dst)
end
def prepare_assets(src, dst)
cp_r(src, dst)
Find.find(dst).select do |file|
ext = File.extname(file).downcase
File.unlink(file) if File.file?(file) && SKIP_ASSETS.include?(ext)
ext == '.css'
end
end
def prepare_js(js, dst)
js.map do |file|
npath = File.join(dst, File.basename(file))
FileUtils.cp(file, npath)
npath
end
end
def make_devhelp_file(doc, src, dst, unlink = false)
structure = parse_index(src)
File.write(dst, build_devhelp(doc, structure))
File.unlink(src)
structure
end
def downloader
@downloader ||= Downloader.new
end
def document_structure(doc, type)
doc.internal_subset.remove
doc.create_internal_subset('html', nil, nil)
unless doc.at_css('head')
title = Nokogiri::XML::Node.new 'head', doc
doc.root.children.first.add_previous_sibling title
end
body = doc.at_css('body')
content = body.children.remove
body.add_child <<-EOF
<section class="_container _devhelp">
<div class="_content _#{type}">
#{content}
</div>
</section>
EOF
end
def set_title(doc, text = nil)
unless text
h1 = doc.at_css('h1, h2, h3, h4, h5')
return unless h1
text = h1.text.strip
end
title = doc.at_css('title')
unless title
title = Nokogiri::XML::Node.new 'title', doc
doc.at_css('head').add_child(title)
end
title.content = text
end
def inject_assets(doc, path, css, js, skip)
head = doc.at_css('head')
level = ['..'] * path[skip].count(File::SEPARATOR)
css.each do |asset|
link = Nokogiri::XML::Node.new 'link', doc
link['rel'] = 'stylesheet'
link['media'] = 'all'
link['charset'] = 'UTF-8'
link['href'] = File.join(level + [asset[skip]])
head.add_child(link)
end
body = doc.at_css('body')
js.each do |asset|
script = Nokogiri::XML::Node.new 'script', doc
script['type'] = 'text/javascript'
script['charset'] = 'UTF-8'
script['src'] = File.join(level + [asset[skip]])
body.add_child(script)
end
end
def src_for(doc)
src_path = File.join(@options[:base_path], doc.path)
unless File.exists?(src_path)
puts %(ERROR: can't find "#{doc.name}" documentation files. Please download/scrape it first.)
return nil
end
src_path
end
def dst_for(doc)
dst_path = File.join(@options[:devhelp_path], doc.path)
if File.exists?(dst_path)
unless @options[:force]
puts %(ERROR: #{doc.name} was already converted. Use --force to overwrite.)
return nil
end
FileUtils.rm_rf(dst_path)
end
dst_path
end
def for_doc(doc)
src_path = src_for(doc) || return
dst_path = dst_for(doc) || return
cp_r(src_path, dst_path)
css = prepare_assets(@options[:asset_path], File.join(dst_path, 'assets'))
js = prepare_js(@options[:js], File.join(dst_path, 'assets'))
titles = make_devhelp_file(doc,
File.join(dst_path, @options[:index]),
File.join(dst_path, "#{doc.slug}.devhelp2"),
true)
skip = (dst_path.length + 1) .. -1
downloader.processor do |file, parser|
document_structure(parser, doc.type)
set_title(parser, titles[:files][file[skip]])
inject_assets(parser, file, css, js, skip)
end
Find.find(dst_path).
select(&method(:is_document?)).
each {|d| downloader.process_page(nil, d)}
downloader.run
end
def is_document?(p)
!File.basename(p).starts_with?('.') && p.ends_with?('.html') && File.file?(p)
end
def builder(&block)
Nokogiri::XML::Builder.new(encoding: 'UTF-8', &block)
end
def parse_index(path)
structure = {terms: {}, files: {}}
JSON.load(open(path))['entries'].each do |e, o|
structure[:terms][e['name']] = e['path']
unless e['path'].include?('#')
structure[:files][e['path']] = e['name']
end
end
structure
end
end

@ -24,6 +24,9 @@ module Docs
mattr_accessor :store_path mattr_accessor :store_path
self.store_path = File.expand_path '../public/docs', @@root_path self.store_path = File.expand_path '../public/docs', @@root_path
mattr_accessor :devhelp_store_path
self.devhelp_store_path = File.expand_path '../public/devhelp', @@root_path
class DocNotFound < NameError; end class DocNotFound < NameError; end
def self.all def self.all

@ -3,7 +3,7 @@ module Docs
INDEX_FILENAME = 'index.json' INDEX_FILENAME = 'index.json'
class << self class << self
attr_accessor :name, :slug, :type, :version, :abstract attr_accessor :name, :slug, :type, :version, :abstract, :language
def inherited(subclass) def inherited(subclass)
subclass.type = type subclass.type = type

@ -44,7 +44,7 @@ module Docs
elsif ENV['COLUMNS'] elsif ENV['COLUMNS']
ENV['COLUMNS'].to_i ENV['COLUMNS'].to_i
else else
`stty size`.scan(/\d+/).last.to_i `tput cols`.scan(/\d+/).last.to_i
end end
rescue rescue
@terminal_width = nil @terminal_width = nil

@ -15,5 +15,6 @@ module Docs
self.type = 'angular' self.type = 'angular'
self.version = '1.0.7' self.version = '1.0.7'
self.base_url = '' self.base_url = ''
self.language = 'javascript'
end end
end end

@ -5,6 +5,7 @@ module Docs
self.type = 'underscore' self.type = 'underscore'
self.version = '1.1.0' self.version = '1.1.0'
self.base_url = 'http://backbonejs.org' self.base_url = 'http://backbonejs.org'
self.language = 'javascript'
html_filters.push 'backbone/clean_html', 'backbone/entries', 'title' html_filters.push 'backbone/clean_html', 'backbone/entries', 'title'

@ -4,6 +4,7 @@ module Docs
self.type = 'coffeescript' self.type = 'coffeescript'
self.version = '1.6.3' self.version = '1.6.3'
self.base_url = 'http://coffeescript.org' self.base_url = 'http://coffeescript.org'
self.language = 'coffeescript'
html_filters.push 'coffeescript/clean_html', 'coffeescript/entries', 'title' html_filters.push 'coffeescript/clean_html', 'coffeescript/entries', 'title'

@ -6,6 +6,7 @@ module Docs
self.version = '3.4.1' self.version = '3.4.1'
self.base_url = 'https://github.com/mbostock/d3/wiki/' self.base_url = 'https://github.com/mbostock/d3/wiki/'
self.root_path = 'API-Reference' self.root_path = 'API-Reference'
self.language = 'javascript'
html_filters.push 'd3/clean_html', 'd3/entries' html_filters.push 'd3/clean_html', 'd3/entries'

@ -5,6 +5,7 @@ module Docs
self.type = 'ember' self.type = 'ember'
self.version = '1.3.0' self.version = '1.3.0'
self.base_url = 'http://emberjs.com/api/' self.base_url = 'http://emberjs.com/api/'
self.language = 'javascript'
html_filters.push 'ember/clean_html', 'ember/entries', 'title' html_filters.push 'ember/clean_html', 'ember/entries', 'title'

@ -4,6 +4,7 @@ module Docs
self.version = '1.8.5' self.version = '1.8.5'
self.base_url = 'http://git-scm.com/docs' self.base_url = 'http://git-scm.com/docs'
self.initial_paths = %w(/git.html) self.initial_paths = %w(/git.html)
self.language = 'git'
html_filters.push 'git/clean_html', 'git/entries' html_filters.push 'git/clean_html', 'git/entries'

@ -4,6 +4,7 @@ module Docs
self.type = 'rfc' self.type = 'rfc'
self.base_url = 'http://www.w3.org/Protocols/rfc2616/' self.base_url = 'http://www.w3.org/Protocols/rfc2616/'
self.root_path = 'rfc2616.html' self.root_path = 'rfc2616.html'
self.language = 'http'
html_filters.push 'http/clean_html', 'http/entries' html_filters.push 'http/clean_html', 'http/entries'

@ -6,6 +6,7 @@ module Docs
self.version = '3.0.0' self.version = '3.0.0'
self.base_url = 'http://knockoutjs.com/documentation/' self.base_url = 'http://knockoutjs.com/documentation/'
self.root_path = 'introduction.html' self.root_path = 'introduction.html'
self.language = 'javascript'
html_filters.push 'knockout/clean_html', 'knockout/entries' html_filters.push 'knockout/clean_html', 'knockout/entries'

@ -3,6 +3,7 @@ module Docs
self.type = 'less' self.type = 'less'
self.version = '1.6.0' self.version = '1.6.0'
self.base_url = 'http://lesscss.org' self.base_url = 'http://lesscss.org'
self.language = 'less'
html_filters.push 'less/clean_html', 'less/entries', 'title' html_filters.push 'less/clean_html', 'less/entries', 'title'

@ -5,6 +5,7 @@ module Docs
self.type = 'lodash' self.type = 'lodash'
self.version = '2.4.1' self.version = '2.4.1'
self.base_url = 'http://lodash.com/docs' self.base_url = 'http://lodash.com/docs'
self.language = 'javascript'
html_filters.push 'lodash/clean_html', 'lodash/entries', 'title' html_filters.push 'lodash/clean_html', 'lodash/entries', 'title'

@ -5,6 +5,7 @@ module Docs
self.type = 'node' self.type = 'node'
self.version = '0.10.24' self.version = '0.10.24'
self.base_url = 'http://nodejs.org/api/' self.base_url = 'http://nodejs.org/api/'
self.language = 'javascript'
html_filters.push 'node/clean_html', 'node/entries', 'title' html_filters.push 'node/clean_html', 'node/entries', 'title'

@ -16,6 +16,7 @@ module Docs
# Downloaded from php.net/download-docs.php # Downloaded from php.net/download-docs.php
self.dir = '/Users/Thibaut/DevDocs/Docs/PHP' self.dir = '/Users/Thibaut/DevDocs/Docs/PHP'
self.language = 'php'
html_filters.push 'php/internal_urls', 'php/entries', 'php/clean_html', 'title' html_filters.push 'php/internal_urls', 'php/entries', 'php/clean_html', 'title'
text_filters.push 'php/fix_urls' text_filters.push 'php/fix_urls'

@ -7,6 +7,7 @@ module Docs
self.base_url = 'http://www.postgresql.org/docs/9.3/static/' self.base_url = 'http://www.postgresql.org/docs/9.3/static/'
self.root_path = 'reference.html' self.root_path = 'reference.html'
self.initial_paths = %w(sql.html runtime-config.html charset.html) self.initial_paths = %w(sql.html runtime-config.html charset.html)
self.language = 'postgresql'
html_filters.insert_before 'normalize_urls', 'postgresql/clean_nav' html_filters.insert_before 'normalize_urls', 'postgresql/clean_nav'
html_filters.push 'postgresql/clean_html', 'postgresql/entries', 'title' html_filters.push 'postgresql/clean_html', 'postgresql/entries', 'title'

@ -5,6 +5,7 @@ module Docs
self.dir = '/Users/Thibaut/DevDocs/Docs/Python' # downloaded from docs.python.org/3/download.html self.dir = '/Users/Thibaut/DevDocs/Docs/Python' # downloaded from docs.python.org/3/download.html
self.base_url = 'http://docs.python.org/3/' self.base_url = 'http://docs.python.org/3/'
self.root_path = 'library/index.html' self.root_path = 'library/index.html'
self.language = 'python'
html_filters.push 'python/entries', 'python/clean_html' html_filters.push 'python/entries', 'python/clean_html'

@ -3,6 +3,7 @@ module Docs
self.type = 'redis' self.type = 'redis'
self.version = 'up to 2.8.4' self.version = 'up to 2.8.4'
self.base_url = 'http://redis.io/commands' self.base_url = 'http://redis.io/commands'
self.language = 'redis'
html_filters.push 'redis/entries', 'redis/clean_html', 'title' html_filters.push 'redis/entries', 'redis/clean_html', 'title'

@ -4,6 +4,7 @@ module Docs
self.version = '3.2.12' self.version = '3.2.12'
self.base_url = 'http://sass-lang.com/docs/yardoc/' self.base_url = 'http://sass-lang.com/docs/yardoc/'
self.root_path = 'file.SASS_REFERENCE.html' self.root_path = 'file.SASS_REFERENCE.html'
self.language = 'sass'
html_filters.push 'sass/clean_html', 'sass/entries', 'title' html_filters.push 'sass/clean_html', 'sass/entries', 'title'

@ -5,6 +5,7 @@ module Docs
self.type = 'underscore' self.type = 'underscore'
self.version = '1.5.2' self.version = '1.5.2'
self.base_url = 'http://underscorejs.org' self.base_url = 'http://underscorejs.org'
self.language = 'javascript'
html_filters.push 'underscore/clean_html', 'underscore/entries', 'title' html_filters.push 'underscore/clean_html', 'underscore/entries', 'title'

@ -0,0 +1,161 @@
require 'typhoeus'
require 'nokogiri'
require 'delegate'
require 'fileutils'
require 'cgi'
class Downloader < SimpleDelegator
include Typhoeus
MAX_QUEUE_SIZE = 20
def initialize(*args)
super(Hydra.new(*args))
end
def processor(&block)
@processor = block
end
def queue_size
queued_requests.size
end
def file(src, dst, &block)
file = nil
request = Request.new(src)
request.on_headers do |response|
if response.response_code == 200
dname = File.dirname(dst)
FileUtils.mkdir_p(dname) unless File.directory?(dname)
file = open(dst, 'wb')
else
failed(src, dst, response)
end
end
request.on_body do |chunk|
file.write(chunk) if file
end
request.on_complete do |response|
if file
file.close
block.call(dst) if block
end
end
queue request
dst
end
def queue(*args, &block)
run while queue_size > MAX_QUEUE_SIZE
__getobj__(*args, &block)
end
def page(src, target)
file(src, target) { process_page(src, target) }
end
def process_page(src, path)
doc = Nokogiri::HTML.parse(File.read(path), 'UTF-8')
rdir = path.gsub(%r{\.[^./]*$}, '') + '_files'
skip = dirname_range(path)
doc.css('iframe[src], img[src], script[src], link[href][rel="stylesheet"], link[href][rel="shortcut icon"]').each do |elem|
uri = url_join(src, elem['src'] || elem['href'])
case elem.name
when 'iframe'
elem['src'] = page(uri, resource_path_for(rdir, uri, 'html'))[skip]
when 'link'
elem['href'] = file(uri, resource_path_for(rdir, uri, 'css')) do |f|
process_stylesheet_file(uri, f) if elem['rel'] == 'stylesheet'
end[skip]
when 'script'
elem['src'] = file(uri, resource_path_for(rdir, uri, 'js'))[skip]
when 'img'
elem['src'] = file(uri, resource_path_for(rdir, uri, 'png'))[skip]
end
end
doc.css('style').each do |style|
style.content = process_stylesheet(src, style.content, rdir)
end
@processor.call(path, doc) if @processor
File.write(path, doc.to_html)
end
protected
def dirname_range(path, dirname = false)
path = File.dirname(path) unless dirname
l = path.length
l += 1 if l > 0
l..-1
end
def process_stylesheet_file(src, fname)
File.write(fname, process_stylesheet(src, File.read(fname), File.dirname(fname)))
end
def process_stylesheet(src, style, dir)
skip = dirname_range(dir, true)
style = style.gsub(/@import\s*(?:url\s*)?(?:\()?(?:\s*)["']?([^'"\s\)]*)["']?\)?([\w\s\,^\]\(\)]*)\)?[;\n]?/) do
uri = url_join(src, $1)
fname = resource_path_for(dir, uri, 'css')
file(uri, fname) { process_stylesheet_file(uri, fname) }
%{@import url("#{fname[skip]}") #$2;\n}
end
style = style.gsub(/(?!@import )url\s*\(["']?(.+?)["']?\)/) do
uri = url_join(src, $1)
fname = resource_path_for(dir, uri, 'png')
file(uri, fname)
%{url("#{fname[skip]}")}
end
style
end
def url_join(base, new)
if base && new
URI.join(base, new).to_s
else
base || new
end
end
def resource_path_for(dir, resource, ext = nil)
rfile = CGI.unescape(resource.gsub(%r{.*/|[#?].*}, ''))
if rfile.empty?
rfile = "downloaded#{'%04d' % @counter}"
@counter += 1
end
rfile << ".#{ext}" if ext && File.extname(rfile).empty?
prefix = 1
tfile = rfile
puts rfile
loop do
path = File.join(dir, tfile)
break path unless File.exists?(path)
tfile = "#{prefix}_#{rfile}"
prefix += 1
end
end
def failed(src, dst, response)
puts "#{src} -> #{dst} failed: #{response.status_message}"
end
end

@ -109,6 +109,9 @@ class DocsCLI < Thor
desc 'verify (<doc> <doc>... | --all)', 'Verify documentations' desc 'verify (<doc> <doc>... | --all)', 'Verify documentations'
option :all, type: :boolean option :all, type: :boolean
def verify(*names) def verify(*names)
require 'find'
require 'cgi'
docs = options[:all] ? Docs.all : find_docs(names) docs = options[:all] ? Docs.all : find_docs(names)
assert_docs(docs) assert_docs(docs)
docs.each(&method(:verify_doc)) docs.each(&method(:verify_doc))
@ -117,6 +120,36 @@ class DocsCLI < Thor
invalid_doc(error.name) invalid_doc(error.name)
end end
desc 'devhelp-book (<doc> <doc>... | --all)', 'Generate DevHelp book'
option :all, type: :boolean
option :force, type: :boolean
def devhelp_book(*names)
require 'app'
require 'devhelp'
unless File.exists?(App.assets_path)
AssetsCLI.new.invoke(:compile)
end
docs = options[:all] ? Docs.all : find_docs(names)
assert_docs(docs)
js = File.expand_path('../assets/javascripts/vendor', Docs.root_path)
DevHelp.new({
force: options[:force],
base_path: Docs.store_path,
devhelp_path: Docs.devhelp_store_path,
asset_path: App.assets_path,
index: Docs::Doc::INDEX_FILENAME,
js: [File.join(js, 'prism.js'), File.join(js, 'prism-invoke.js')]
}).for_docs(docs)
puts 'Done'
rescue Docs::DocNotFound => error
invalid_doc(error.name)
end
desc 'clean', 'Delete documentation packages' desc 'clean', 'Delete documentation packages'
def clean def clean
File.delete(*Dir[File.join Docs.store_path, '*.tar.gz']) File.delete(*Dir[File.join Docs.store_path, '*.tar.gz'])
@ -215,9 +248,6 @@ class DocsCLI < Thor
skip_path = (doc_path.length + 1)..-1 skip_path = (doc_path.length + 1)..-1
require 'find'
require 'cgi'
Find.find(doc_path) do |path| Find.find(doc_path) do |path|
next unless is_document?(path) next unless is_document?(path)

@ -1 +1 @@
[] [{"name":"Angular.js","slug":"angular","type":"angular","version":"1.0.7","index_path":"angular/index.json","mtime":1376320383},{"name":"Backbone.js","slug":"backbone","type":"underscore","version":"1.1.0","index_path":"backbone/index.json","mtime":1381653808},{"name":"CoffeeScript","slug":"coffeescript","type":"coffeescript","version":"1.6.3","index_path":"coffeescript/index.json","mtime":1381655883},{"name":"CSS","slug":"css","type":"mdn","version":null,"index_path":"css/index.json","mtime":1386415462},{"name":"D3.js","slug":"d3","type":"d3","version":"3.4.1","index_path":"d3/index.json","mtime":1390150423},{"name":"DOM","slug":"dom","type":"mdn","version":null,"index_path":"dom/index.json","mtime":1386432655},{"name":"DOM Events","slug":"dom_events","type":"mdn","version":null,"index_path":"dom_events/index.json","mtime":1381908561},{"name":"Ember.js","slug":"ember","type":"ember","version":"1.3.0","index_path":"ember/index.json","mtime":1389536714},{"name":"Git","slug":"git","type":"git","version":"1.8.5","index_path":"git/index.json","mtime":1386958029},{"name":"HTML","slug":"html","type":"mdn","version":null,"index_path":"html/index.json","mtime":1386413422},{"name":"HTTP","slug":"http","type":"rfc","version":null,"index_path":"http/index.json","mtime":1381675313},{"name":"JavaScript","slug":"javascript","type":"mdn","version":null,"index_path":"javascript/index.json","mtime":1386425468},{"name":"jQuery","slug":"jquery","type":"jquery","version":"up to 2.0.3","index_path":"jquery/index.json","mtime":1388954827},{"name":"jQuery Mobile","slug":"jquerymobile","type":"jquery","version":"1.4.0","index_path":"jquerymobile/index.json","mtime":1388956495},{"name":"jQuery UI","slug":"jqueryui","type":"jquery","version":"1.10.3","index_path":"jqueryui/index.json","mtime":1388955780},{"name":"Knockout.js","slug":"knockout","type":"knockout","version":"3.0.0","index_path":"knockout/index.json","mtime":1390167124},{"name":"Less","slug":"less","type":"less","version":"1.6.0","index_path":"less/index.json","mtime":1388929594},{"name":"Lo-Dash","slug":"lodash","type":"lodash","version":"2.4.1","index_path":"lodash/index.json","mtime":1386144288},{"name":"Node.js","slug":"node","type":"node","version":"0.10.24","index_path":"node/index.json","mtime":1387611542},{"name":"PHP","slug":"php","type":"php","version":"up to 5.5.8","index_path":"php/index.json","mtime":1389535846},{"name":"PostgreSQL","slug":"postgresql","type":"postgres","version":"up to 9.3.2","index_path":"postgresql/index.json","mtime":1387061550},{"name":"Python","slug":"python","type":"sphinx","version":"3.3.3","index_path":"python/index.json","mtime":1385469242},{"name":"Ruby on Rails","slug":"rails","type":"rdoc","version":"4.0.2","index_path":"rails/index.json","mtime":1386228555},{"name":"Redis","slug":"redis","type":"redis","version":"up to 2.8.4","index_path":"redis/index.json","mtime":1389660376},{"name":"Ruby","slug":"ruby","type":"rdoc","version":"2.1.0","index_path":"ruby/index.json","mtime":1388961427},{"name":"Sass","slug":"sass","type":"yard","version":"3.2.12","index_path":"sass/index.json","mtime":1381679946},{"name":"Underscore.js","slug":"underscore","type":"underscore","version":"1.5.2","index_path":"underscore/index.json","mtime":1381654139}]
Loading…
Cancel
Save