guides 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +3 -0
  3. data/bin/guides +6 -0
  4. data/guides.gemspec +32 -0
  5. data/lib/guides.rb +25 -0
  6. data/lib/guides/cli.rb +35 -0
  7. data/lib/guides/generator.rb +274 -0
  8. data/lib/guides/helpers.rb +55 -0
  9. data/lib/guides/indexer.rb +69 -0
  10. data/lib/guides/levenshtein.rb +31 -0
  11. data/lib/guides/new.rb +26 -0
  12. data/lib/guides/templates/assets/images/book_icon.gif +0 -0
  13. data/lib/guides/templates/assets/images/bullet.gif +0 -0
  14. data/lib/guides/templates/assets/images/chapters_icon.gif +0 -0
  15. data/lib/guides/templates/assets/images/check_bullet.gif +0 -0
  16. data/lib/guides/templates/assets/images/construction.png +0 -0
  17. data/lib/guides/templates/assets/images/construction.svg +123 -0
  18. data/lib/guides/templates/assets/images/credits_pic_blank.gif +0 -0
  19. data/lib/guides/templates/assets/images/edge_badge.png +0 -0
  20. data/lib/guides/templates/assets/images/feature_tile.gif +0 -0
  21. data/lib/guides/templates/assets/images/footer_tile.gif +0 -0
  22. data/lib/guides/templates/assets/images/grey_bullet.gif +0 -0
  23. data/lib/guides/templates/assets/images/header_backdrop.png +0 -0
  24. data/lib/guides/templates/assets/images/header_tile.gif +0 -0
  25. data/lib/guides/templates/assets/images/icons/README +5 -0
  26. data/lib/guides/templates/assets/images/icons/callouts/1.png +0 -0
  27. data/lib/guides/templates/assets/images/icons/callouts/10.png +0 -0
  28. data/lib/guides/templates/assets/images/icons/callouts/11.png +0 -0
  29. data/lib/guides/templates/assets/images/icons/callouts/12.png +0 -0
  30. data/lib/guides/templates/assets/images/icons/callouts/13.png +0 -0
  31. data/lib/guides/templates/assets/images/icons/callouts/14.png +0 -0
  32. data/lib/guides/templates/assets/images/icons/callouts/15.png +0 -0
  33. data/lib/guides/templates/assets/images/icons/callouts/2.png +0 -0
  34. data/lib/guides/templates/assets/images/icons/callouts/3.png +0 -0
  35. data/lib/guides/templates/assets/images/icons/callouts/4.png +0 -0
  36. data/lib/guides/templates/assets/images/icons/callouts/5.png +0 -0
  37. data/lib/guides/templates/assets/images/icons/callouts/6.png +0 -0
  38. data/lib/guides/templates/assets/images/icons/callouts/7.png +0 -0
  39. data/lib/guides/templates/assets/images/icons/callouts/8.png +0 -0
  40. data/lib/guides/templates/assets/images/icons/callouts/9.png +0 -0
  41. data/lib/guides/templates/assets/images/icons/caution.png +0 -0
  42. data/lib/guides/templates/assets/images/icons/example.png +0 -0
  43. data/lib/guides/templates/assets/images/icons/home.png +0 -0
  44. data/lib/guides/templates/assets/images/icons/important.png +0 -0
  45. data/lib/guides/templates/assets/images/icons/next.png +0 -0
  46. data/lib/guides/templates/assets/images/icons/note.png +0 -0
  47. data/lib/guides/templates/assets/images/icons/prev.png +0 -0
  48. data/lib/guides/templates/assets/images/icons/tip.png +0 -0
  49. data/lib/guides/templates/assets/images/icons/up.png +0 -0
  50. data/lib/guides/templates/assets/images/icons/warning.png +0 -0
  51. data/lib/guides/templates/assets/images/nav_arrow.gif +0 -0
  52. data/lib/guides/templates/assets/images/tab_grey.gif +0 -0
  53. data/lib/guides/templates/assets/images/tab_info.gif +0 -0
  54. data/lib/guides/templates/assets/images/tab_note.gif +0 -0
  55. data/lib/guides/templates/assets/images/tab_red.gif +0 -0
  56. data/lib/guides/templates/assets/images/tab_yellow.gif +0 -0
  57. data/lib/guides/templates/assets/images/tab_yellow.png +0 -0
  58. data/lib/guides/templates/assets/javascripts/guides.js +9 -0
  59. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushAS3.js +59 -0
  60. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushAppleScript.js +75 -0
  61. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushBash.js +59 -0
  62. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushCSharp.js +65 -0
  63. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushColdFusion.js +100 -0
  64. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushCpp.js +97 -0
  65. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushCss.js +91 -0
  66. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushDelphi.js +55 -0
  67. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushDiff.js +41 -0
  68. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushErlang.js +52 -0
  69. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushGroovy.js +67 -0
  70. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushJScript.js +52 -0
  71. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushJava.js +57 -0
  72. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushJavaFX.js +58 -0
  73. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPerl.js +72 -0
  74. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPhp.js +88 -0
  75. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPlain.js +33 -0
  76. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPowerShell.js +74 -0
  77. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushPython.js +64 -0
  78. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushRuby.js +55 -0
  79. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushSass.js +94 -0
  80. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushScala.js +51 -0
  81. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushSql.js +66 -0
  82. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushVb.js +56 -0
  83. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shBrushXml.js +69 -0
  84. data/lib/guides/templates/assets/javascripts/syntaxhighlighter/shCore.js +17 -0
  85. data/lib/guides/templates/assets/stylesheets/main.css +445 -0
  86. data/lib/guides/templates/assets/stylesheets/print.css +52 -0
  87. data/lib/guides/templates/assets/stylesheets/reset.css +43 -0
  88. data/lib/guides/templates/assets/stylesheets/style.css +13 -0
  89. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCore.css +226 -0
  90. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreDefault.css +328 -0
  91. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreDjango.css +331 -0
  92. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreEclipse.css +339 -0
  93. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreEmacs.css +324 -0
  94. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreFadeToGrey.css +328 -0
  95. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreMDUltra.css +324 -0
  96. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreMidnight.css +324 -0
  97. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shCoreRDark.css +324 -0
  98. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeDefault.css +117 -0
  99. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeDjango.css +120 -0
  100. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeEclipse.css +128 -0
  101. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeEmacs.css +113 -0
  102. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeFadeToGrey.css +117 -0
  103. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeMDUltra.css +113 -0
  104. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeMidnight.css +113 -0
  105. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeRDark.css +113 -0
  106. data/lib/guides/templates/assets/stylesheets/syntaxhighlighter/shThemeRailsGuides.css +116 -0
  107. data/lib/guides/templates/guides.yml.tt +35 -0
  108. data/lib/guides/templates/source/_clickable_index.html.erb +17 -0
  109. data/lib/guides/templates/source/_full_index.html.erb +16 -0
  110. data/lib/guides/templates/source/_sections.html.erb +24 -0
  111. data/lib/guides/templates/source/contribute.textile +47 -0
  112. data/lib/guides/templates/source/credits.html.erb +21 -0
  113. data/lib/guides/templates/source/index.html.erb +1 -0
  114. data/lib/guides/templates/source/layout.html.erb +82 -0
  115. data/lib/guides/textile_extensions.rb +39 -0
  116. data/lib/guides/version.rb +3 -0
  117. data/lib/guides/w3c_validator.rb +89 -0
  118. metadata +257 -0
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ devbin
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "guides"
4
+ require "guides/cli"
5
+
6
+ Guides::CLI.start
@@ -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
+
@@ -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
@@ -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