docubot 0.0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/docubot +83 -14
- data/lib/docubot.rb +33 -10
- data/lib/docubot/bundle.rb +68 -0
- data/lib/docubot/converter.rb +13 -9
- data/lib/docubot/converters/haml.rb +9 -0
- data/lib/docubot/converters/html.rb +5 -0
- data/lib/docubot/converters/markdown.rb +12 -4
- data/lib/docubot/converters/raw_code.rb +5 -0
- data/lib/docubot/converters/textile.rb +10 -0
- data/lib/docubot/glossary.rb +44 -0
- data/lib/docubot/index.rb +66 -0
- data/lib/docubot/page.rb +142 -18
- data/lib/docubot/shells/default/0_License.md +60 -0
- data/lib/docubot/shells/default/Appendix/Glossary.md +5 -0
- data/lib/docubot/shells/default/Appendix/Index_Page.md +8 -0
- data/lib/docubot/shells/default/Appendix/Table of Contents.md +5 -0
- data/lib/docubot/shells/default/_static/logo.png +0 -0
- data/lib/docubot/shells/default/index.txt +3 -0
- data/lib/docubot/shells/docubot-help/0_License.md +21 -0
- data/lib/docubot/shells/docubot-help/1_Getting_Started.md +48 -0
- data/lib/docubot/templates/default/page.haml b/data/lib/docubot/shells/docubot-help/2_Basic_Concepts/0 The → Metasection.md +0 -0
- data/lib/docubot/shells/docubot-help/2_Basic_Concepts/1 Interpage Links.md +1 -0
- data/lib/docubot/shells/docubot-help/2_Basic_Concepts/2 Generating the Output.md +1 -0
- data/lib/docubot/shells/docubot-help/2_Basic_Concepts/3 Customizing Templates.md +1 -0
- data/lib/docubot/shells/docubot-help/2_Basic_Concepts/4 Adding Images.md +2 -0
- data/lib/docubot/shells/docubot-help/2_Basic_Concepts/index.md +6 -0
- data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling Glossary.md +3 -0
- data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling Indexing.md +10 -0
- data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling the Table of Contents.md +7 -0
- data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Switching Page Templates.md +1 -0
- data/lib/docubot/shells/docubot-help/4_Appendix/Glossary.md +5 -0
- data/lib/docubot/shells/docubot-help/4_Appendix/Index_Page.md +6 -0
- data/lib/docubot/shells/docubot-help/4_Appendix/Table of Contents.md +7 -0
- data/lib/docubot/shells/docubot-help/_glossary/Template.md +1 -0
- data/lib/docubot/shells/docubot-help/_static/glider.png +0 -0
- data/lib/docubot/shells/docubot-help/index.txt +8 -0
- data/lib/docubot/shells/nvphysx/0_License.md +3 -0
- data/lib/docubot/shells/nvphysx/1_Getting_Started.haml +51 -0
- data/lib/docubot/shells/nvphysx/Appendix/Glossary.md +7 -0
- data/lib/docubot/shells/nvphysx/_glossary/APEX.md +1 -0
- data/lib/docubot/shells/nvphysx/_glossary/NVIDIA.md +1 -0
- data/lib/docubot/shells/nvphysx/_glossary/PhysX.textile +3 -0
- data/lib/docubot/shells/nvphysx/_static/NVBadge_3D.png +0 -0
- data/lib/docubot/shells/nvphysx/_static/PhysXbyNV_Black.png +0 -0
- data/lib/docubot/shells/nvphysx/_templates/_root/bg_green_bar_revised.gif +0 -0
- data/lib/docubot/shells/nvphysx/_templates/_root/close.png +0 -0
- data/lib/docubot/shells/nvphysx/_templates/_root/common.css +264 -0
- data/lib/docubot/shells/nvphysx/_templates/_root/glossary.css +4 -0
- data/lib/docubot/shells/nvphysx/_templates/_root/glossary.js +24 -0
- data/lib/docubot/shells/nvphysx/_templates/_root/nvdevtools.js +31 -0
- data/lib/docubot/shells/nvphysx/_templates/_root/nvidia-logo.gif +0 -0
- data/lib/docubot/shells/nvphysx/_templates/_root/right-sidebar.png +0 -0
- data/lib/docubot/shells/nvphysx/_templates/top.haml +28 -0
- data/lib/docubot/shells/nvphysx/index.txt +5 -0
- data/lib/docubot/snippet.rb +4 -3
- data/lib/docubot/snippets/glossary.rb +6 -3
- data/lib/docubot/snippets/index_entries.rb +7 -0
- data/lib/docubot/templates/_root/common.css +107 -0
- data/lib/docubot/templates/_root/toc.css +5 -0
- data/lib/docubot/templates/_root/toc.js +4 -0
- data/lib/docubot/templates/glossary.haml +5 -0
- data/lib/docubot/templates/index.haml +14 -0
- data/lib/docubot/templates/page.haml +1 -0
- data/lib/docubot/templates/section.haml +10 -0
- data/lib/docubot/templates/toc.haml +10 -0
- data/lib/docubot/templates/top.haml +25 -0
- data/lib/docubot/writer.rb +24 -0
- data/lib/docubot/writers/chm.rb +73 -0
- data/lib/docubot/writers/chm/hhc.erb +27 -0
- data/lib/docubot/writers/chm/hhk.erb +28 -0
- data/lib/docubot/writers/chm/hhp.erb +23 -0
- data/lib/docubot/writers/html.rb +75 -0
- data/test/all.rb +2 -0
- data/test/site1/A Slight Change of Heart/3_more_crap.haml +17 -0
- data/test/site1/appendices/gkheadftw.html +2 -0
- data/test/site1/appendices/index.md +2 -0
- data/test/site1/preamble.haml +4 -0
- data/test/site1/raw.textile +10 -0
- metadata +88 -14
- data/lib/docubot/generator.rb +0 -35
- data/lib/docubot/section.rb +0 -16
- data/lib/docubot/template.rb +0 -13
- data/lib/docubot/templates/default/section.haml +0 -0
- data/test/site1/A Slight Change of Heart/3_more_crap.md +0 -5
- data/test/site1/raw.md +0 -3
data/bin/docubot
CHANGED
@@ -1,36 +1,105 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
1
|
+
#!/usr/bin/env ruby -KU
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
# Hack to allow the binary to be run without the gem installed
|
5
|
+
$: << File.join( File.dirname( __FILE__ ), '..', 'lib' )
|
2
6
|
|
3
7
|
require 'rubygems'
|
4
8
|
require 'docubot'
|
5
9
|
|
6
10
|
USAGE = <<ENDUSAGE
|
7
11
|
Usage:
|
8
|
-
docubot [-
|
12
|
+
docubot [-h] [create [-s shell] [-f]] directory [-w writer] [-o output_file]
|
9
13
|
ENDUSAGE
|
10
14
|
|
11
|
-
|
15
|
+
HELP = <<ENDHELP
|
16
|
+
-h, --help Show this help.
|
17
|
+
create Create a starter directory filled with example files;
|
18
|
+
also copies the template for easy modification, if desired.
|
19
|
+
-s, --shell The shell to copy from.
|
20
|
+
Available shells: #{DocuBot::SHELLS.join(', ')}
|
21
|
+
-f, --force Force create over an existing directory,
|
22
|
+
deleting any existing files.
|
23
|
+
-w, --writer The output type to create [Defaults to 'chm']
|
24
|
+
Available writers: #{DocuBot::Writer::INSTALLED_WRITERS.join(', ')}
|
25
|
+
-o, --output The file or folder (depending on the writer) to create.
|
26
|
+
[Default value depends on the writer chosen.]
|
27
|
+
|
28
|
+
ENDHELP
|
29
|
+
|
30
|
+
ARGS = { :shell=>'default', :writer=>'chm' }
|
12
31
|
UNFLAGGED_ARGS = [ :directory ]
|
13
32
|
next_arg = UNFLAGGED_ARGS.first
|
14
33
|
ARGV.each{ |arg|
|
15
34
|
case arg
|
16
35
|
when '-h','--help'
|
17
36
|
ARGS[:help] = true
|
18
|
-
when '
|
19
|
-
|
37
|
+
when 'create'
|
38
|
+
ARGS[:create] = true
|
39
|
+
when '-f','--force'
|
40
|
+
ARGS[:force] = true
|
41
|
+
when '-s','--shell'
|
42
|
+
next_arg = :shell
|
43
|
+
when '-w','--writer'
|
44
|
+
next_arg = :writer
|
45
|
+
when '-o','--output'
|
46
|
+
next_arg = :output
|
20
47
|
else
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
48
|
+
if next_arg
|
49
|
+
ARGS[next_arg] = arg
|
50
|
+
UNFLAGGED_ARGS.delete( next_arg )
|
51
|
+
end
|
52
|
+
next_arg = UNFLAGGED_ARGS.first
|
26
53
|
end
|
27
54
|
}
|
28
55
|
|
29
56
|
if ARGS[:help] or !ARGS[:directory]
|
30
|
-
|
31
|
-
|
32
|
-
|
57
|
+
puts USAGE
|
58
|
+
puts HELP if ARGS[:help]
|
59
|
+
exit
|
33
60
|
end
|
34
61
|
|
35
|
-
|
62
|
+
if ARGS[:create]
|
63
|
+
require 'fileutils'
|
64
|
+
|
65
|
+
unless DocuBot::SHELLS.include?( ARGS[:shell] )
|
66
|
+
puts " Error: '#{ARGS[:shell]}' is not a valid shell.",
|
67
|
+
" Available shells: #{DocuBot::SHELLS.join(', ')}",
|
68
|
+
" (Shells are installed in #{DocuBot::DIR/'shells'})", ""
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
|
72
|
+
if File.exist?( ARGS[:directory] )
|
73
|
+
if ARGS[:force]
|
74
|
+
# TODO: confirmation?
|
75
|
+
# TODO: May be able to just use :force=>true for cp_r
|
76
|
+
FileUtils.rm_rf( ARGS[:directory] )
|
77
|
+
else
|
78
|
+
puts " Error: directory '#{ARGS[:directory]}' already exists.",
|
79
|
+
" Use the --force option to forcibly overwrite.", ""
|
80
|
+
exit 1
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
dest = File.expand_path( ARGS[:directory] )
|
85
|
+
src = File.expand_path( DocuBot::SHELL_DIR/ARGS[:shell] )
|
86
|
+
puts " Creating: #{dest}",
|
87
|
+
" as copy of: #{src}", ""
|
88
|
+
|
89
|
+
# Copy template files first so that the shell can overwrite if it wants
|
90
|
+
FileUtils.mkdir_p( dest )
|
91
|
+
FileUtils.cp_r( DocuBot::TEMPLATE_DIR, dest/'_templates' )
|
92
|
+
|
93
|
+
Dir.chdir src do
|
94
|
+
Dir['**/*.*'].each do |file|
|
95
|
+
dest_file = dest/file
|
96
|
+
dest_dir = File.dirname(dest_file)
|
97
|
+
FileUtils.mkdir_p( dest_dir )
|
98
|
+
FileUtils.cp( file, dest_file )
|
99
|
+
end
|
100
|
+
end
|
101
|
+
else
|
102
|
+
bundle = DocuBot::Bundle.new( ARGS[:directory] )
|
103
|
+
bundle.write( ARGS[:writer], ARGS[:output] )
|
104
|
+
end
|
36
105
|
|
data/lib/docubot.rb
CHANGED
@@ -1,14 +1,37 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Assume all files read in are UTF-8 format
|
3
|
+
if Object.const_defined? "Encoding"
|
4
|
+
Encoding.default_external = Encoding.default_internal = 'UTF-8'
|
5
|
+
end
|
6
|
+
|
7
|
+
# Wicked monkey patch to avoid File.join verbosity everywhere
|
8
|
+
class String
|
9
|
+
def / ( other )
|
10
|
+
File.join( self, other )
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'fileutils'
|
15
|
+
|
16
|
+
module FileUtils
|
17
|
+
def self.win_path( path )
|
18
|
+
path.gsub( '/', '\\' )
|
7
19
|
end
|
8
20
|
end
|
21
|
+
|
22
|
+
module DocuBot
|
23
|
+
VERSION = '0.2'
|
24
|
+
DIR = File.expand_path( File.dirname( __FILE__ ) )
|
25
|
+
|
26
|
+
TEMPLATE_DIR = DIR / 'docubot/templates'
|
27
|
+
SHELL_DIR = DIR / 'docubot/shells'
|
28
|
+
Dir.chdir( SHELL_DIR ){ SHELLS = Dir['*'] }
|
29
|
+
end
|
30
|
+
|
31
|
+
require 'docubot/snippet'
|
9
32
|
require 'docubot/converter'
|
10
|
-
require 'docubot/
|
33
|
+
require 'docubot/writer'
|
11
34
|
require 'docubot/page'
|
12
|
-
require 'docubot/
|
13
|
-
require 'docubot/
|
14
|
-
require 'docubot/
|
35
|
+
require 'docubot/glossary'
|
36
|
+
require 'docubot/index'
|
37
|
+
require 'docubot/bundle'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
class DocuBot::Bundle
|
3
|
+
attr_reader :toc, :extras, :glossary, :index, :source
|
4
|
+
|
5
|
+
def initialize( source_directory )
|
6
|
+
@source = File.expand_path( source_directory )
|
7
|
+
raise "DocuBot cannot find directory #{@source}. Exiting." unless File.exists?( @source )
|
8
|
+
@extras = []
|
9
|
+
@glossary = DocuBot::Glossary.new( self, @source/'_glossary' )
|
10
|
+
@index = DocuBot::Index.new( self )
|
11
|
+
Dir.chdir( @source ) do
|
12
|
+
@toc = DocuBot::Page.new( ".", "Table of Contents" )
|
13
|
+
@toc.bundle = self
|
14
|
+
@toc.meta['glossary'] = @glossary
|
15
|
+
@toc.meta['index'] = @index
|
16
|
+
pages_by_path = { '.'=>@toc }
|
17
|
+
|
18
|
+
files_and_folders = Dir[ '**/*' ]
|
19
|
+
files_and_folders.reject!{ |f| File.basename(f) =~ /^index\.[^.]+$/ || File.basename(f) == '_static' || File.basename(f) == '_glossary' }
|
20
|
+
files_and_folders.reject!{ |f| f =~ /\b_templates\b/ }
|
21
|
+
files_and_folders.each do |item|
|
22
|
+
extension = File.extname( item )[ 1..-1 ]
|
23
|
+
item_is_page = File.directory?(item) || DocuBot::Converter.by_type[extension]
|
24
|
+
if item_is_page
|
25
|
+
parent = pages_by_path[ File.dirname( item ) ]
|
26
|
+
page = DocuBot::Page.new( item )
|
27
|
+
page.bundle = self
|
28
|
+
pages_by_path[ item ] = page
|
29
|
+
parent << page if parent
|
30
|
+
if item =~ /\b_glossary\b/
|
31
|
+
@glossary << page
|
32
|
+
end
|
33
|
+
@index.process_page( page )
|
34
|
+
|
35
|
+
# TODO: Move this bloat elsewhere.
|
36
|
+
if page.toc?
|
37
|
+
html = page.to_html
|
38
|
+
page.toc.scan /[a-z][\w.:-]*/ do |id|
|
39
|
+
# TODO: Maybe a lightweight HTML parser would be faster here? (Certainly more robust.)
|
40
|
+
if title = html[/\bid *= *['"]#{id}['"][^>]*>([^<]+)/,1]
|
41
|
+
page << DocuBot::SubLink.new( page, title.strip, id )
|
42
|
+
else
|
43
|
+
warn "Could not find requested toc anchor '##{id}' on #{page.html_path}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
else
|
49
|
+
# TODO: Anything better needed?
|
50
|
+
@extras << item
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def write( writer_type, destination=nil)
|
57
|
+
writer = DocuBot::Writer.by_type[ writer_type.to_s.downcase ]
|
58
|
+
if writer
|
59
|
+
writer.new( self ).write( destination )
|
60
|
+
unless @glossary.missing_terms.empty?
|
61
|
+
warn "The following glossary terms were never defined:\n#{@glossary.missing_terms.map{|t|t.inspect}.join(', ')}"
|
62
|
+
end
|
63
|
+
else
|
64
|
+
raise "Unknown writer '#{writer_type}'; available types: #{DocuBot::Writer::INSTALLED_WRITERS.join ', '}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
data/lib/docubot/converter.rb
CHANGED
@@ -1,21 +1,25 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
module DocuBot
|
2
3
|
module Converter
|
3
|
-
|
4
|
-
def
|
5
|
-
types.each{ |type|
|
4
|
+
@by_type = {}
|
5
|
+
def self.to_convert( *types, &block )
|
6
|
+
types.each{ |type| @by_type[type.to_s] = block }
|
6
7
|
end
|
7
8
|
def self.by_type
|
8
|
-
|
9
|
+
@by_type
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
12
|
-
def self.convert_to_html( source, type )
|
13
|
-
converter = DocuBot::Converter.by_type[ type.to_s ]
|
14
|
-
|
15
|
-
|
13
|
+
def self.convert_to_html( page, source, type )
|
14
|
+
if converter = DocuBot::Converter.by_type[ type.to_s ]
|
15
|
+
puts "Converting #{type}: #{source.inspect[0..60]}" if $DEBUG
|
16
|
+
converter[ page, source ]
|
17
|
+
else
|
18
|
+
raise "No converter found for type #{type}"
|
19
|
+
end
|
16
20
|
end
|
17
21
|
end
|
18
22
|
|
19
|
-
Dir[
|
23
|
+
Dir[ DocuBot::DIR/'docubot/converters/*.rb' ].each do |converter|
|
20
24
|
require converter
|
21
25
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'haml'
|
4
|
+
options = { :format=>:html4, :ugly=>true }
|
5
|
+
options.merge!( :encoding=>'utf-8' ) if Object.const_defined? "Encoding"
|
6
|
+
|
7
|
+
DocuBot::Converter.to_convert :haml do |page, source|
|
8
|
+
Haml::Engine.new( source, options ).render( page, :page=>page, :global=>page.bundle.toc, :root=>page.root )
|
9
|
+
end
|
@@ -1,6 +1,14 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
require 'rubygems'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
begin
|
4
|
+
require 'bluecloth'
|
5
|
+
DocuBot::Converter.to_convert :md, :markdown do |page, source|
|
6
|
+
# BlueCloth 2.0.5 takes UTF-8 source and returns ASCII-8BIT
|
7
|
+
result = BlueCloth.new(source).to_html
|
8
|
+
result.encode!( 'UTF-8', :undef=>:replace ) if Object.const_defined? "Encoding"
|
9
|
+
result
|
10
|
+
end
|
11
|
+
rescue LoadError
|
12
|
+
warn "Unable to load bluecloth gem; *.markdown/*.md markup will not be recognized as a page."
|
6
13
|
end
|
14
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'rubygems'
|
3
|
+
begin
|
4
|
+
require 'redcloth'
|
5
|
+
DocuBot::Converter.to_convert :textile, :rc do |page, source|
|
6
|
+
RedCloth.new(source,[:no_span_caps]).to_html
|
7
|
+
end
|
8
|
+
rescue LoadError
|
9
|
+
warn "Unable to load RedCloth gem; *.textile/*.rc markup will not be recognized as a page."
|
10
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
class DocuBot::Glossary
|
3
|
+
attr_accessor :bundle
|
4
|
+
def initialize( bundle, dir )
|
5
|
+
@entries = {}
|
6
|
+
@downcased = {}
|
7
|
+
@bundle = bundle
|
8
|
+
@missing = []
|
9
|
+
# .directory? also ensures that the path exists
|
10
|
+
if File.directory?( dir )
|
11
|
+
@directory = File.expand_path( dir )
|
12
|
+
# Dir[ dir/'*' ].each do |item|
|
13
|
+
# page = DocuBot::Page.new( item )
|
14
|
+
# page.bundle = @bundle
|
15
|
+
# self << page
|
16
|
+
# end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def []=( term, definition )
|
20
|
+
@entries[ term ] = definition
|
21
|
+
@downcased[ term.downcase ] = term
|
22
|
+
end
|
23
|
+
def []( term )
|
24
|
+
@entries[ @downcased[ term.downcase ] ]
|
25
|
+
end
|
26
|
+
def each
|
27
|
+
@entries.each{ |term,defn| yield term, defn }
|
28
|
+
end
|
29
|
+
def add_missing_term( term )
|
30
|
+
@missing << term.downcase
|
31
|
+
# File.open( @directory/"#{term}.md", "w" ){ |f| f << "<span class='todo'>TODO: define #{term}</span>" } if @directory
|
32
|
+
end
|
33
|
+
def missing_terms
|
34
|
+
# Terms may have been defined after being first seen
|
35
|
+
@missing.reject{ |term| self[term] }.uniq
|
36
|
+
end
|
37
|
+
def <<( page )
|
38
|
+
#TODO: perhaps don't serialize the page here, but wait until some #write call gives us a template so we can use that?
|
39
|
+
self[ page.title ] = page.to_html
|
40
|
+
end
|
41
|
+
def to_js
|
42
|
+
"$glossaryTerms = {#{@entries.map{ |term,defn| "'#{term.downcase.gsub("'","\\\\'")}':'#{defn.gsub("'","\\\\'").gsub(/[\r\n]/,'\\n')}'" }.join(",\n")}};"
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
# The index links keywords to a particular page.
|
4
|
+
#
|
5
|
+
# Keywords are added to the index by:
|
6
|
+
# * Having a "keywords" entry in the metasection, e.g.
|
7
|
+
# keywords: Rigid Bodies, Dynamic, Physical Mesh
|
8
|
+
# * Surrounding a word or phrase on the page with two @ characters, e.g.
|
9
|
+
# The purpose of a @@physical mesh@@ is to...
|
10
|
+
# * Having an "index: headings" entry in the metasection, causing each
|
11
|
+
# heading on the page to be added to the index.
|
12
|
+
# * Having an "index: definitions" entry in the metasection, causing each
|
13
|
+
# <dt>...</dt> on the page to be added to the index.
|
14
|
+
# (May be combined with the above as "index: headings definitions".)
|
15
|
+
#
|
16
|
+
# As shown above, terms may be referenced in title or lowercase.
|
17
|
+
# Names with capital letters will be favored over lowercase in the index.
|
18
|
+
class DocuBot::Index
|
19
|
+
attr_reader :entries
|
20
|
+
def initialize( bundle )
|
21
|
+
@bundle = bundle
|
22
|
+
@entries = Hash.new{|h,k|h[k]=[]} # key points to array of DocuBot::Pages
|
23
|
+
@downcased = {}
|
24
|
+
#TODO: support links to sub-sections instead of just pages
|
25
|
+
end
|
26
|
+
|
27
|
+
# Run through the 'keywords' and 'index' meta attribute for a page and add entries
|
28
|
+
# Note: in-content @@keyword@@ marks are processed by snippets/index_entries.rb
|
29
|
+
def process_page( page )
|
30
|
+
page.keywords.split(/,\s*/).each{ |key| add( key, page ) } if page.keywords?
|
31
|
+
|
32
|
+
html = page.to_html
|
33
|
+
unless page['no-index'] && page['no-index'].include?( 'headings' )
|
34
|
+
#TODO: Fix the regex to use a backreference to ensure the correct closing tag, once 1.8x support is not necessary
|
35
|
+
html.scan( %r{<h[1-6][^>]*>(.+?)</h[1-6]>}im ){ |captures| add( captures.first, page ) }
|
36
|
+
end
|
37
|
+
|
38
|
+
unless page['no-index'] && page['no-index'].include?( 'definitions' )
|
39
|
+
html.scan( %r{<dt[^>]*>(.+?)</dt>}im ){ |captures| add captures.first, page }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def add( term, page )
|
44
|
+
term.strip!
|
45
|
+
term.gsub!(/<[^>]+>/,'')
|
46
|
+
down = term.downcase
|
47
|
+
if existing = @downcased[ down ]
|
48
|
+
# The existing entry might be early-arriving all-lowercase.
|
49
|
+
# If the new term has more capital letters, it wins.
|
50
|
+
if term.scan(/[A-Z]/).length > existing.scan(/[A-Z]/).length
|
51
|
+
@downcased[ down ] = term
|
52
|
+
@entries[ term ] = @entries[ existing ]
|
53
|
+
@entries.delete( existing )
|
54
|
+
else
|
55
|
+
term = existing
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@entries[ term ] << page
|
59
|
+
@entries[ term ].uniq!
|
60
|
+
@downcased[ down ] = term
|
61
|
+
end
|
62
|
+
|
63
|
+
def each
|
64
|
+
@entries.each{ |term, pages| yield term, pages }
|
65
|
+
end
|
66
|
+
end
|
data/lib/docubot/page.rb
CHANGED
@@ -1,33 +1,157 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
require 'yaml'
|
2
3
|
class DocuBot::Page
|
3
|
-
META_SEPARATOR =
|
4
|
+
META_SEPARATOR = /^\+\+\+\s*$/ # Sort of like +++ATH0
|
4
5
|
|
5
|
-
attr_reader :
|
6
|
+
attr_reader :pages, :type, :folder, :file, :meta
|
7
|
+
attr_accessor :parent, :bundle
|
6
8
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
def initialize( source_path, title=nil, type=nil )
|
10
|
+
puts "#{self.class}.new( #{source_path.inspect}, #{title.inspect}, #{type.inspect} )" if $DEBUG
|
11
|
+
title ||= File.basename( source_path ).sub( /\.[^.]+$/, '' ).gsub( '_', ' ' ).sub( /^\d+\s/, '' )
|
12
|
+
@meta = { 'title'=>title }
|
13
|
+
@pages = []
|
14
|
+
@file = source_path
|
15
|
+
if File.directory?( @file )
|
16
|
+
@folder = @file
|
17
|
+
# WILL SET @file TO NIL FOR DIRECTORIES WITHOUT AN INDEX.* FILE
|
18
|
+
@file = Dir[ source_path/'index.*' ][0]
|
19
|
+
else
|
20
|
+
@folder = File.dirname( @file )
|
21
|
+
end
|
12
22
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
23
|
+
# Directories without an index file have no @file
|
24
|
+
if @file
|
25
|
+
@type = type || File.extname( @file )[ 1..-1 ]
|
26
|
+
parts = IO.read( @file ).split( META_SEPARATOR, 2 )
|
27
|
+
|
28
|
+
if parts.length > 1
|
29
|
+
# Make YAML friendler to n00bs
|
30
|
+
yaml = YAML.load( parts.first.gsub( /^\t/, ' ' ) )
|
31
|
+
@meta.merge!( yaml )
|
32
|
+
end
|
33
|
+
|
34
|
+
# Raw markup, untransformed
|
35
|
+
@raw = parts.last
|
36
|
+
end
|
37
|
+
end
|
38
|
+
def []( key )
|
39
|
+
@meta[key]
|
19
40
|
end
|
20
41
|
|
21
42
|
def method_missing( method, *args )
|
22
43
|
key=method.to_s
|
23
|
-
case key[-1..-1]
|
44
|
+
case key[-1..-1] # the last character of the method name
|
24
45
|
when '?' then @meta.has_key?( key[0..-2] )
|
25
|
-
when '
|
46
|
+
#when '=' then @meta[ key[0..-2] ] = args[0]
|
47
|
+
when '!','=' then super
|
26
48
|
else @meta[ key ]
|
27
49
|
end
|
28
50
|
end
|
29
|
-
|
30
|
-
|
31
|
-
|
51
|
+
def ancestors
|
52
|
+
page = self
|
53
|
+
anc = []
|
54
|
+
anc.unshift( page ) while page = page.parent
|
55
|
+
anc
|
56
|
+
end
|
57
|
+
def sections
|
58
|
+
@pages.reject{ |e| e.pages.empty? }
|
59
|
+
end
|
60
|
+
def leafs
|
61
|
+
@pages.select{ |e| e.pages.empty? }
|
62
|
+
end
|
63
|
+
def every_leaf
|
64
|
+
(leafs + sub_sections.map{ |sub| sub.every_leaf }).flatten
|
65
|
+
end
|
66
|
+
def every_section
|
67
|
+
(sub_sections + sub_sections.map{ |sub| sub.every_section }).flatten
|
68
|
+
end
|
69
|
+
def descendants
|
70
|
+
(@pages + @pages.map{ |page| page.descendants }).flatten
|
71
|
+
end
|
72
|
+
alias_method :every_page, :descendants
|
73
|
+
def <<( entry )
|
74
|
+
@pages << entry
|
75
|
+
entry.parent = self
|
76
|
+
end
|
77
|
+
def leaf?
|
78
|
+
@pages.empty? || @pages.all?{ |x| x.is_a?(DocuBot::SubLink) }
|
79
|
+
end
|
80
|
+
def depth
|
81
|
+
@_depth ||= @file ? @file.count('/') : @folder.count('/') + 1
|
82
|
+
end
|
83
|
+
def root
|
84
|
+
@_root ||= "../" * depth
|
85
|
+
end
|
86
|
+
def html_path
|
87
|
+
@file ? @file.sub( /[^.]+$/, 'html' ) : ( @folder / 'index.html' )
|
88
|
+
end
|
89
|
+
def to_html
|
90
|
+
return @cached_html if @cached_html
|
91
|
+
|
92
|
+
contents = if @raw
|
93
|
+
# Directories with no index.* file will not have any @raw
|
94
|
+
html = DocuBot::convert_to_html( self, @raw, @type )
|
95
|
+
DocuBot::process_snippets( self, html )
|
96
|
+
end
|
97
|
+
|
98
|
+
@meta['template'] ||= leaf? ? 'page' : 'section'
|
99
|
+
|
100
|
+
master_templates = DocuBot::TEMPLATE_DIR
|
101
|
+
source_templates = @bundle.source / '_templates'
|
102
|
+
|
103
|
+
haml = source_templates / "#{template}.haml"
|
104
|
+
haml = master_templates / "#{template}.haml" unless File.exists?( haml )
|
105
|
+
haml = master_templates / "page.haml" unless File.exists?( haml )
|
106
|
+
haml = Haml::Engine.new( IO.read( haml ), DocuBot::Writer::HAML_OPTIONS )
|
107
|
+
contents = haml.render( Object.new, :contents=>contents, :page=>self, :global=>@bundle.toc, :root=>root )
|
108
|
+
|
109
|
+
@cached_html = contents
|
110
|
+
end
|
111
|
+
def to_html!
|
112
|
+
@cached_html=nil
|
113
|
+
to_html
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class DocuBot::SubLink
|
118
|
+
attr_reader :page, :title, :id
|
119
|
+
def initialize( page, title, id )
|
120
|
+
@page, @title, @id = page, title, id
|
121
|
+
end
|
122
|
+
def html_path
|
123
|
+
"#{@page.html_path}##{@id}"
|
124
|
+
end
|
125
|
+
def leaf?
|
126
|
+
true
|
127
|
+
end
|
128
|
+
def pages
|
129
|
+
[]
|
130
|
+
end
|
131
|
+
alias_method :descendants, :pages
|
132
|
+
def depth
|
133
|
+
@page.depth
|
134
|
+
end
|
135
|
+
def parent
|
136
|
+
@page
|
137
|
+
end
|
138
|
+
def parent=( page )
|
139
|
+
@page = page
|
140
|
+
end
|
141
|
+
def to_html
|
142
|
+
""
|
143
|
+
end
|
144
|
+
def ancestors
|
145
|
+
@page.ancestors
|
146
|
+
end
|
147
|
+
alias_method :to_html!, :to_html
|
148
|
+
def method_missing(*args)
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
def hide
|
152
|
+
false
|
153
|
+
end
|
154
|
+
def sublink?
|
155
|
+
true
|
32
156
|
end
|
33
157
|
end
|