guides 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/bin/guides +6 -0
- data/guides.gemspec +32 -0
- data/lib/guides.rb +25 -0
- data/lib/guides/cli.rb +35 -0
- data/lib/guides/generator.rb +274 -0
- data/lib/guides/helpers.rb +55 -0
- data/lib/guides/indexer.rb +69 -0
- data/lib/guides/levenshtein.rb +31 -0
- data/lib/guides/new.rb +26 -0
- data/lib/guides/templates/assets/images/book_icon.gif +0 -0
- data/lib/guides/templates/assets/images/bullet.gif +0 -0
- data/lib/guides/templates/assets/images/chapters_icon.gif +0 -0
- data/lib/guides/templates/assets/images/check_bullet.gif +0 -0
- data/lib/guides/templates/assets/images/construction.png +0 -0
- data/lib/guides/templates/assets/images/construction.svg +123 -0
- data/lib/guides/templates/assets/images/credits_pic_blank.gif +0 -0
- data/lib/guides/templates/assets/images/edge_badge.png +0 -0
- data/lib/guides/templates/assets/images/feature_tile.gif +0 -0
- data/lib/guides/templates/assets/images/footer_tile.gif +0 -0
- data/lib/guides/templates/assets/images/grey_bullet.gif +0 -0
- data/lib/guides/templates/assets/images/header_backdrop.png +0 -0
- data/lib/guides/templates/assets/images/header_tile.gif +0 -0
- data/lib/guides/templates/assets/images/icons/README +5 -0
- data/lib/guides/templates/assets/images/icons/callouts/1.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/10.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/11.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/12.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/13.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/14.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/15.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/2.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/3.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/4.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/5.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/6.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/7.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/8.png +0 -0
- data/lib/guides/templates/assets/images/icons/callouts/9.png +0 -0
- data/lib/guides/templates/assets/images/icons/caution.png +0 -0
- data/lib/guides/templates/assets/images/icons/example.png +0 -0
- data/lib/guides/templates/assets/images/icons/home.png +0 -0
- data/lib/guides/templates/assets/images/icons/important.png +0 -0
- data/lib/guides/templates/assets/images/icons/next.png +0 -0
- data/lib/guides/templates/assets/images/icons/note.png +0 -0
- data/lib/guides/templates/assets/images/icons/prev.png +0 -0
- data/lib/guides/templates/assets/images/icons/tip.png +0 -0
- data/lib/guides/templates/assets/images/icons/up.png +0 -0
- data/lib/guides/templates/assets/images/icons/warning.png +0 -0
- data/lib/guides/templates/assets/images/nav_arrow.gif +0 -0
- data/lib/guides/templates/assets/images/tab_grey.gif +0 -0
- data/lib/guides/templates/assets/images/tab_info.gif +0 -0
- data/lib/guides/templates/assets/images/tab_note.gif +0 -0
- data/lib/guides/templates/assets/images/tab_red.gif +0 -0
- data/lib/guides/templates/assets/images/tab_yellow.gif +0 -0
- data/lib/guides/templates/assets/images/tab_yellow.png +0 -0
- data/lib/guides/templates/assets/javascripts/guides.js +9 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushAS3.js +59 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js +75 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushBash.js +59 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushCSharp.js +65 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js +100 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushCpp.js +97 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushCss.js +91 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushDelphi.js +55 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushDiff.js +41 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushErlang.js +52 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushGroovy.js +67 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushJScript.js +52 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushJava.js +57 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js +58 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPerl.js +72 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPhp.js +88 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPlain.js +33 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js +74 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPython.js +64 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushRuby.js +55 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushSass.js +94 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushScala.js +51 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushSql.js +66 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushVb.js +56 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushXml.js +69 -0
- data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shCore.js +17 -0
- data/lib/guides/templates/assets/stylesheets/main.css +445 -0
- data/lib/guides/templates/assets/stylesheets/print.css +52 -0
- data/lib/guides/templates/assets/stylesheets/reset.css +43 -0
- data/lib/guides/templates/assets/stylesheets/style.css +13 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCore.css +226 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreDefault.css +328 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreDjango.css +331 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css +339 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css +324 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css +328 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css +324 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css +324 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreRDark.css +324 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeDefault.css +117 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeDjango.css +120 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css +128 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css +113 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css +117 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css +113 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css +113 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeRDark.css +113 -0
- data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css +116 -0
- data/lib/guides/templates/guides.yml.tt +35 -0
- data/lib/guides/templates/source/_clickable_index.html.erb +17 -0
- data/lib/guides/templates/source/_full_index.html.erb +16 -0
- data/lib/guides/templates/source/_sections.html.erb +24 -0
- data/lib/guides/templates/source/contribute.textile +47 -0
- data/lib/guides/templates/source/credits.html.erb +21 -0
- data/lib/guides/templates/source/index.html.erb +1 -0
- data/lib/guides/templates/source/layout.html.erb +82 -0
- data/lib/guides/textile_extensions.rb +39 -0
- data/lib/guides/version.rb +3 -0
- data/lib/guides/w3c_validator.rb +89 -0
- metadata +257 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/bin/guides
ADDED
data/guides.gemspec
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib/', __FILE__)
|
|
3
|
+
$:.unshift lib unless $:.include?(lib)
|
|
4
|
+
|
|
5
|
+
require 'guides/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |s|
|
|
8
|
+
s.name = "guides"
|
|
9
|
+
s.version = Guides::VERSION
|
|
10
|
+
s.platform = Gem::Platform::RUBY
|
|
11
|
+
s.authors = ["Yehuda Katz"]
|
|
12
|
+
s.email = ["wycats@gmail.com"]
|
|
13
|
+
s.homepage = "http://yehudakatz.com"
|
|
14
|
+
s.summary = %q{Extracting the Rails Guides framework for the rest of us}
|
|
15
|
+
s.description = %q{A tool for creating version controlled guides for open source projects, based on the Rails Guides framework}
|
|
16
|
+
|
|
17
|
+
s.required_rubygems_version = ">= 1.3.6"
|
|
18
|
+
s.rubyforge_project = "guides"
|
|
19
|
+
|
|
20
|
+
s.add_dependency "actionpack", "~> 3.0.0"
|
|
21
|
+
s.add_dependency "activesupport", "~> 3.0.0"
|
|
22
|
+
s.add_dependency "rack", "~> 1.2.1"
|
|
23
|
+
s.add_dependency "RedCloth", "~> 4.1.1"
|
|
24
|
+
s.add_dependency "thor", "~> 0.14.6"
|
|
25
|
+
|
|
26
|
+
s.files = `git ls-files`.split("\n")
|
|
27
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
28
|
+
s.executables = %w(guides)
|
|
29
|
+
s.default_executable = "guides"
|
|
30
|
+
s.require_paths = ["lib"]
|
|
31
|
+
end
|
|
32
|
+
|
data/lib/guides.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require "action_pack"
|
|
2
|
+
require "redcloth"
|
|
3
|
+
|
|
4
|
+
require "guides/textile_extensions"
|
|
5
|
+
require "guides/generator"
|
|
6
|
+
|
|
7
|
+
module Guides
|
|
8
|
+
class << self
|
|
9
|
+
def root
|
|
10
|
+
# TODO: Search for guides.yml
|
|
11
|
+
File.expand_path(Dir.pwd)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def meta
|
|
15
|
+
@meta ||= begin
|
|
16
|
+
if File.exist?("#{root}/guides.yml")
|
|
17
|
+
YAML.load_file("#{root}/guides.yml")
|
|
18
|
+
# TODO: Sanity check the output
|
|
19
|
+
else
|
|
20
|
+
raise "#{root}/guides.yml was not found"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
data/lib/guides/cli.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require "thor"
|
|
2
|
+
require "guides/new"
|
|
3
|
+
|
|
4
|
+
module Guides
|
|
5
|
+
class CLI < Thor
|
|
6
|
+
ASSETS_ROOT = File.expand_path("../assets", __FILE__)
|
|
7
|
+
SOURCE_ROOT = File.expand_path("../source", __FILE__)
|
|
8
|
+
|
|
9
|
+
desc "new NAME", "create a new directory of guides"
|
|
10
|
+
method_option "name", :type => :string
|
|
11
|
+
def new(name)
|
|
12
|
+
invoke "guides:new:copy", [name, options[:name] || name]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "generate", "generate the guides output"
|
|
16
|
+
method_option "only", :type => :array
|
|
17
|
+
method_option "clean", :type => :boolean
|
|
18
|
+
def generate
|
|
19
|
+
FileUtils.rm_rf("#{Guides.root}/output") if options[:clean]
|
|
20
|
+
require "guides/generator"
|
|
21
|
+
|
|
22
|
+
opts = options.dup
|
|
23
|
+
|
|
24
|
+
opts[:only] ||= []
|
|
25
|
+
|
|
26
|
+
generator = Guides::Generator.new(opts)
|
|
27
|
+
generator.generate
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc "preview", "preview the guides as you work"
|
|
31
|
+
def preview
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# ---------------------------------------------------------------------------
|
|
2
|
+
#
|
|
3
|
+
# This script generates the guides. It can be invoked either directly or via the
|
|
4
|
+
# generate_guides rake task within the railties directory.
|
|
5
|
+
#
|
|
6
|
+
# Guides are taken from the source directory, and the resulting HTML goes into the
|
|
7
|
+
# output directory. Assets are stored under files, and copied to output/files as
|
|
8
|
+
# part of the generation process.
|
|
9
|
+
#
|
|
10
|
+
# Some arguments may be passed via environment variables:
|
|
11
|
+
#
|
|
12
|
+
# WARNINGS
|
|
13
|
+
# If you are writing a guide, please work always with WARNINGS=1. Users can
|
|
14
|
+
# generate the guides, and thus this flag is off by default.
|
|
15
|
+
#
|
|
16
|
+
# Internal links (anchors) are checked. If a reference is broken levenshtein
|
|
17
|
+
# distance is used to suggest an existing one. This is useful since IDs are
|
|
18
|
+
# generated by Textile from headers and thus edits alter them.
|
|
19
|
+
#
|
|
20
|
+
# Also detects duplicated IDs. They happen if there are headers with the same
|
|
21
|
+
# text. Please do resolve them, if any, so guides are valid XHTML.
|
|
22
|
+
#
|
|
23
|
+
# ALL
|
|
24
|
+
# Set to "1" to force the generation of all guides.
|
|
25
|
+
#
|
|
26
|
+
# ONLY
|
|
27
|
+
# Use ONLY if you want to generate only one or a set of guides. Prefixes are
|
|
28
|
+
# enough:
|
|
29
|
+
#
|
|
30
|
+
# # generates only association_basics.html
|
|
31
|
+
# ONLY=assoc ruby rails_guides.rb
|
|
32
|
+
#
|
|
33
|
+
# Separate many using commas:
|
|
34
|
+
#
|
|
35
|
+
# # generates only association_basics.html and migrations.html
|
|
36
|
+
# ONLY=assoc,migrations ruby rails_guides.rb
|
|
37
|
+
#
|
|
38
|
+
# Note that if you are working on a guide generation will by default process
|
|
39
|
+
# only that one, so ONLY is rarely used nowadays.
|
|
40
|
+
#
|
|
41
|
+
# EDGE
|
|
42
|
+
# Set to "1" to indicate generated guides should be marked as edge. This
|
|
43
|
+
# inserts a badge and changes the preamble of the home page.
|
|
44
|
+
#
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
require 'set'
|
|
48
|
+
require 'fileutils'
|
|
49
|
+
require 'yaml'
|
|
50
|
+
|
|
51
|
+
require 'active_support/core_ext/string/output_safety'
|
|
52
|
+
require 'active_support/core_ext/object/blank'
|
|
53
|
+
require 'action_controller'
|
|
54
|
+
require 'action_view'
|
|
55
|
+
|
|
56
|
+
require 'guides/indexer'
|
|
57
|
+
require 'guides/helpers'
|
|
58
|
+
require 'guides/levenshtein'
|
|
59
|
+
|
|
60
|
+
module Guides
|
|
61
|
+
class Generator
|
|
62
|
+
attr_reader :guides_dir, :source_dir, :output_dir, :edge, :warnings, :all
|
|
63
|
+
|
|
64
|
+
GUIDES_RE = /\.(?:textile|html\.erb)$/
|
|
65
|
+
LOCAL_ASSETS = File.expand_path("../templates/assets", __FILE__)
|
|
66
|
+
|
|
67
|
+
def initialize(options)
|
|
68
|
+
@options = options
|
|
69
|
+
|
|
70
|
+
@guides_dir = File.expand_path(Dir.pwd)
|
|
71
|
+
@source_dir = File.join(@guides_dir, "source")
|
|
72
|
+
@output_dir = File.join(@guides_dir, "output")
|
|
73
|
+
|
|
74
|
+
FileUtils.mkdir_p(@output_dir)
|
|
75
|
+
|
|
76
|
+
@edge = options[:edge]
|
|
77
|
+
@warnings = options[:warnings]
|
|
78
|
+
@all = options[:all]
|
|
79
|
+
|
|
80
|
+
@meta = Guides.meta
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def generate
|
|
84
|
+
generate_guides
|
|
85
|
+
copy_assets
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
def generate_guides
|
|
90
|
+
guides_to_generate.each do |guide|
|
|
91
|
+
next if guide =~ /(_.*|layout)\.html\.erb$/
|
|
92
|
+
output_file = guide.sub(GUIDES_RE, '.html')
|
|
93
|
+
generate_guide(guide, output_file)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def guides_to_generate
|
|
98
|
+
guides = Dir.entries(source_dir).grep(GUIDES_RE)
|
|
99
|
+
|
|
100
|
+
guides.select do |guide|
|
|
101
|
+
if @options[:only].empty?
|
|
102
|
+
true
|
|
103
|
+
else
|
|
104
|
+
@options[:only].any? { |prefix| guide.start_with?(prefix) }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def copy_assets
|
|
110
|
+
FileUtils.cp_r(Dir["#{LOCAL_ASSETS}/*"], output_dir)
|
|
111
|
+
FileUtils.cp_r(Dir["#{guides_dir}/assets/*"], output_dir)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def generate?(source_file, output_file)
|
|
115
|
+
fin = File.join(source_dir, source_file)
|
|
116
|
+
fout = File.join(output_dir, output_file)
|
|
117
|
+
all || !File.exists?(fout) || File.mtime(fout) < File.mtime(fin)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def generate_guide(guide, output_file)
|
|
121
|
+
return unless generate?(guide, output_file)
|
|
122
|
+
|
|
123
|
+
puts "Generating #{output_file}"
|
|
124
|
+
File.open(File.join(output_dir, output_file), 'w') do |f|
|
|
125
|
+
view = ActionView::Base.new(source_dir, :edge => edge)
|
|
126
|
+
view.extend(Helpers)
|
|
127
|
+
|
|
128
|
+
if guide =~ /\.html\.erb$/
|
|
129
|
+
# Generate the special pages like the home.
|
|
130
|
+
view.render("sections")
|
|
131
|
+
type = @edge ? "edge" : "normal"
|
|
132
|
+
result = view.render(:layout => 'layout', :file => guide, :locals => {:guide_type => type})
|
|
133
|
+
else
|
|
134
|
+
body = File.read(File.join(source_dir, guide))
|
|
135
|
+
body = set_header_section(body, view)
|
|
136
|
+
body = set_index(body, view)
|
|
137
|
+
|
|
138
|
+
result = view.render(:layout => 'layout', :text => textile(body))
|
|
139
|
+
|
|
140
|
+
warn_about_broken_links(result) if @warnings
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
f.write result
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def set_header_section(body, view)
|
|
148
|
+
new_body = body.gsub(/(.*?)endprologue\./m, '').strip
|
|
149
|
+
header = $1
|
|
150
|
+
|
|
151
|
+
header =~ /h2\.(.*)/
|
|
152
|
+
page_title = "#{@meta["title"]}: #{$1.strip}"
|
|
153
|
+
|
|
154
|
+
header = textile(header)
|
|
155
|
+
|
|
156
|
+
view.content_for(:page_title) { page_title.html_safe }
|
|
157
|
+
view.content_for(:header_section) { header.html_safe }
|
|
158
|
+
new_body
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def set_index(body, view)
|
|
162
|
+
index = <<-INDEX
|
|
163
|
+
<div id="subCol">
|
|
164
|
+
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
|
|
165
|
+
<ol class="chapters">
|
|
166
|
+
INDEX
|
|
167
|
+
|
|
168
|
+
i = Indexer.new(body, warnings)
|
|
169
|
+
i.index
|
|
170
|
+
|
|
171
|
+
# Set index for 2 levels
|
|
172
|
+
i.level_hash.each do |key, value|
|
|
173
|
+
link = view.content_tag(:a, :href => key[:id]) { textile(key[:title], true).html_safe }
|
|
174
|
+
|
|
175
|
+
children = value.keys.map do |k|
|
|
176
|
+
view.content_tag(:li,
|
|
177
|
+
view.content_tag(:a, :href => k[:id]) { textile(k[:title], true).html_safe })
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
children_ul = children.empty? ? "" : view.content_tag(:ul, children.join(" ").html_safe)
|
|
181
|
+
|
|
182
|
+
index << view.content_tag(:li, link.html_safe + children_ul.html_safe)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
index << '</ol>'
|
|
186
|
+
index << '</div>'
|
|
187
|
+
|
|
188
|
+
view.content_for(:index_section) { index.html_safe }
|
|
189
|
+
|
|
190
|
+
i.result
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def textile(body, lite_mode=false)
|
|
194
|
+
# If the issue with notextile is fixed just remove the wrapper.
|
|
195
|
+
with_workaround_for_notextile(body) do |body|
|
|
196
|
+
t = RedCloth.new(body)
|
|
197
|
+
t.hard_breaks = false
|
|
198
|
+
t.lite_mode = lite_mode
|
|
199
|
+
t.to_html(:notestuff, :plusplus, :code, :tip)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# For some reason the notextile tag does not always turn off textile. See
|
|
204
|
+
# LH ticket of the security guide (#7). As a temporary workaround we deal
|
|
205
|
+
# with code blocks by hand.
|
|
206
|
+
def with_workaround_for_notextile(body)
|
|
207
|
+
code_blocks = []
|
|
208
|
+
|
|
209
|
+
body.gsub!(%r{<(yaml|shell|ruby|erb|html|sql|plain|javascript)>(.*?)</\1>}m) do |m|
|
|
210
|
+
brush = case $1
|
|
211
|
+
when 'ruby', 'sql', 'javascript', 'plain'
|
|
212
|
+
$1
|
|
213
|
+
when 'erb'
|
|
214
|
+
'ruby; html-script: true'
|
|
215
|
+
when 'html'
|
|
216
|
+
'xml' # html is understood, but there are .xml rules in the CSS
|
|
217
|
+
else
|
|
218
|
+
'plain'
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
code_blocks.push(<<HTML)
|
|
222
|
+
<notextile>
|
|
223
|
+
<div class="code_container">
|
|
224
|
+
<pre class="brush: #{brush}; gutter: false; toolbar: false">
|
|
225
|
+
#{ERB::Util.h($2).strip}
|
|
226
|
+
</pre>
|
|
227
|
+
</div>
|
|
228
|
+
</notextile>
|
|
229
|
+
HTML
|
|
230
|
+
"\ndirty_workaround_for_notextile_#{code_blocks.size - 1}\n"
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
body = yield body
|
|
234
|
+
|
|
235
|
+
body.gsub(%r{<p>dirty_workaround_for_notextile_(\d+)</p>}) do |_|
|
|
236
|
+
code_blocks[$1.to_i]
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def warn_about_broken_links(html)
|
|
241
|
+
anchors = extract_anchors(html)
|
|
242
|
+
check_fragment_identifiers(html, anchors)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def extract_anchors(html)
|
|
246
|
+
# Textile generates headers with IDs computed from titles.
|
|
247
|
+
anchors = Set.new
|
|
248
|
+
html.scan(/<h\d\s+id="([^"]+)/).flatten.each do |anchor|
|
|
249
|
+
if anchors.member?(anchor)
|
|
250
|
+
puts "*** DUPLICATE ID: #{anchor}, please put and explicit ID, e.g. h4(#explicit-id), or consider rewording"
|
|
251
|
+
else
|
|
252
|
+
anchors << anchor
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Footnotes.
|
|
257
|
+
anchors += Set.new(html.scan(/<p\s+class="footnote"\s+id="([^"]+)/).flatten)
|
|
258
|
+
anchors += Set.new(html.scan(/<sup\s+class="footnote"\s+id="([^"]+)/).flatten)
|
|
259
|
+
return anchors
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def check_fragment_identifiers(html, anchors)
|
|
263
|
+
html.scan(/<a\s+href="#([^"]+)/).flatten.each do |fragment_identifier|
|
|
264
|
+
next if fragment_identifier == 'mainCol' # in layout, jumps to some DIV
|
|
265
|
+
unless anchors.member?(fragment_identifier)
|
|
266
|
+
guess = anchors.min { |a, b|
|
|
267
|
+
Levenshtein.distance(fragment_identifier, a) <=> Levenshtein.distance(fragment_identifier, b)
|
|
268
|
+
}
|
|
269
|
+
puts "*** BROKEN LINK: ##{fragment_identifier}, perhaps you meant ##{guess}."
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module Guides
|
|
2
|
+
module Helpers
|
|
3
|
+
def full_index
|
|
4
|
+
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def clickable_index
|
|
8
|
+
guides = Guides.meta["index"]
|
|
9
|
+
|
|
10
|
+
total_guides = guides.inject(0) do |sum, (name, guides)|
|
|
11
|
+
sum + guides.size
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
lgroup, rgroup, counted_guides = {}, {}, 0
|
|
15
|
+
|
|
16
|
+
guides.each do |name, guides|
|
|
17
|
+
if counted_guides > (total_guides / 2.0)
|
|
18
|
+
rgroup[name] = guides
|
|
19
|
+
else
|
|
20
|
+
lgroup[name] = guides
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
counted_guides += guides.size
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
render "clickable_index", :lgroup => lgroup, :rgroup => rgroup
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def guide(name, url, options = {}, &block)
|
|
30
|
+
link = content_tag(:a, :href => url) { name }
|
|
31
|
+
result = content_tag(:dt, link)
|
|
32
|
+
|
|
33
|
+
if options[:work_in_progress]
|
|
34
|
+
result << content_tag(:dd, 'Work in progress', :class => 'work-in-progress')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
result << content_tag(:dd, capture(&block))
|
|
38
|
+
result
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def author(name, nick, image = 'credits_pic_blank.gif', &block)
|
|
42
|
+
image = "images/#{image}"
|
|
43
|
+
|
|
44
|
+
result = content_tag(:img, nil, :src => image, :class => 'left pic', :alt => name)
|
|
45
|
+
result << content_tag(:h3, name)
|
|
46
|
+
result << content_tag(:p, capture(&block))
|
|
47
|
+
content_tag(:div, result, :class => 'clearfix', :id => nick)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def code(&block)
|
|
51
|
+
c = capture(&block)
|
|
52
|
+
content_tag(:code, c)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
|
2
|
+
require 'active_support/ordered_hash'
|
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
|
4
|
+
|
|
5
|
+
module Guides
|
|
6
|
+
class Indexer
|
|
7
|
+
attr_reader :body, :result, :warnings, :level_hash
|
|
8
|
+
|
|
9
|
+
def initialize(body, warnings)
|
|
10
|
+
@body = body
|
|
11
|
+
@result = @body.dup
|
|
12
|
+
@warnings = warnings
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def index
|
|
16
|
+
@level_hash = process(body)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def process(string, current_level=3, counters=[1])
|
|
22
|
+
s = StringScanner.new(string)
|
|
23
|
+
|
|
24
|
+
level_hash = ActiveSupport::OrderedHash.new
|
|
25
|
+
|
|
26
|
+
while !s.eos?
|
|
27
|
+
re = %r{^h(\d)(?:\((#.*?)\))?\s*\.\s*(.*)$}
|
|
28
|
+
s.match?(re)
|
|
29
|
+
if matched = s.matched
|
|
30
|
+
matched =~ re
|
|
31
|
+
level, idx, title = $1.to_i, $2, $3.strip
|
|
32
|
+
|
|
33
|
+
if level < current_level
|
|
34
|
+
# This is needed. Go figure.
|
|
35
|
+
return level_hash
|
|
36
|
+
elsif level == current_level
|
|
37
|
+
index = counters.join(".")
|
|
38
|
+
idx ||= '#' + title_to_idx(title)
|
|
39
|
+
|
|
40
|
+
raise "Parsing Fail" unless @result.sub!(matched, "h#{level}(#{idx}). #{index} #{title}")
|
|
41
|
+
|
|
42
|
+
key = {
|
|
43
|
+
:title => title,
|
|
44
|
+
:id => idx
|
|
45
|
+
}
|
|
46
|
+
# Recurse
|
|
47
|
+
counters << 1
|
|
48
|
+
level_hash[key] = process(s.post_match, current_level + 1, counters)
|
|
49
|
+
counters.pop
|
|
50
|
+
|
|
51
|
+
# Increment the current level
|
|
52
|
+
last = counters.pop
|
|
53
|
+
counters << last + 1
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
s.getch
|
|
57
|
+
end
|
|
58
|
+
level_hash
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def title_to_idx(title)
|
|
62
|
+
idx = title.strip.parameterize.sub(/^\d+/, '')
|
|
63
|
+
if warnings && idx.blank?
|
|
64
|
+
puts "BLANK ID: please put an explicit ID for section #{title}, as in h5(#my-id)"
|
|
65
|
+
end
|
|
66
|
+
idx
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|