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.
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