docubot 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/docubot +125 -125
- data/lib/docubot.rb +43 -43
- data/lib/docubot/bundle.rb +182 -182
- data/lib/docubot/converter.rb +28 -28
- data/lib/docubot/converters/haml.rb +9 -9
- data/lib/docubot/converters/markdown.rb +14 -14
- data/lib/docubot/index.rb +67 -67
- data/lib/docubot/link_tree.rb +111 -111
- data/lib/docubot/metasection.rb +61 -61
- data/lib/docubot/page.rb +167 -167
- data/lib/docubot/shells/default/0_License.md +59 -59
- data/lib/docubot/shells/default/Appendix/Glossary.md +4 -4
- data/lib/docubot/shells/default/Appendix/Index_Page.md +8 -8
- data/lib/docubot/shells/default/Appendix/Table of Contents.md +4 -4
- data/lib/docubot/shells/docubot-help/0_License.md +20 -20
- data/lib/docubot/shells/docubot-help/1_Getting_Started.md +47 -47
- data/lib/docubot/shells/docubot-help/2_Basic_Concepts/4 Adding Images.md +1 -1
- data/lib/docubot/shells/docubot-help/2_Basic_Concepts/index.md +5 -5
- data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling Glossary.md +2 -2
- data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling Indexing.md +9 -9
- data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling the Table of Contents.md +5 -5
- data/lib/docubot/shells/docubot-help/4_Appendix/Glossary.md +4 -4
- data/lib/docubot/shells/docubot-help/4_Appendix/Index_Page.md +5 -5
- data/lib/docubot/shells/docubot-help/4_Appendix/Table of Contents.md +7 -7
- data/lib/docubot/shells/docubot-help/index.txt +7 -7
- data/lib/docubot/snippet.rb +19 -19
- data/lib/docubot/snippets/glossary.rb +7 -7
- data/lib/docubot/snippets/index_entries.rb +6 -6
- data/lib/docubot/templates/_root/glossary.css +3 -3
- data/lib/docubot/templates/_root/glossary.js +57 -57
- data/lib/docubot/templates/index.haml +14 -14
- data/lib/docubot/templates/toc.haml +2 -2
- data/lib/docubot/templates/top.haml +32 -32
- data/lib/docubot/writers/chm/hhc.erb +1 -1
- data/lib/docubot/writers/chm/hhk.erb +2 -2
- data/lib/docubot/writers/html.rb +87 -87
- data/spec/_all.rb +12 -12
- data/spec/_helper.rb +16 -16
- data/spec/bundle.rb +339 -339
- data/spec/command.rb +3 -3
- data/spec/converters.rb +2 -2
- data/spec/global.rb +27 -27
- data/spec/glossary.rb +94 -94
- data/spec/index.rb +2 -2
- data/spec/page.rb +80 -80
- data/spec/samples/attributes/defaults.haml +34 -34
- data/spec/samples/attributes/explicit1.haml +42 -42
- data/spec/samples/attributes/explicit2.haml +41 -41
- data/spec/samples/attributes/hidden.haml +39 -39
- data/spec/samples/attributes/index.md +8 -8
- data/spec/samples/collisions/page1.md +2 -2
- data/spec/samples/collisions/page1.textile +2 -2
- data/spec/samples/collisions/page2.haml +3 -3
- data/spec/samples/collisions/page2.html +2 -2
- data/spec/samples/collisions/page2.txt +2 -2
- data/spec/samples/collisions/page3.md +2 -2
- data/spec/samples/files/index.md +1 -1
- data/spec/samples/glossary/Glossary.txt +4 -4
- data/spec/samples/glossary/Some Page.md +2 -2
- data/spec/samples/glossary/_glossary/Simple Term.md +2 -2
- data/spec/samples/glossary/_glossary/complex.haml +8 -8
- data/spec/samples/glossary/_glossary/project_x.md +3 -3
- data/spec/samples/hierarchy/1/1.1/1.1.1/index.haml +1 -1
- data/spec/samples/hierarchy/1/1.1/1.1.1/page.haml +3 -3
- data/spec/samples/hierarchy/1/1.1/page.haml +3 -3
- data/spec/samples/hierarchy/1/page.haml +3 -3
- data/spec/samples/hierarchy/2/2.1/2.1.1/page.haml +3 -3
- data/spec/samples/hierarchy/2/2.1/index.haml +1 -1
- data/spec/samples/hierarchy/2/2.1/page.haml +3 -3
- data/spec/samples/hierarchy/2/page.haml +3 -3
- data/spec/samples/hierarchy/main.haml +1 -1
- data/spec/samples/hierarchy/toc.md +1 -1
- data/spec/samples/links/index.txt +11 -11
- data/spec/samples/links/root.md +13 -13
- data/spec/samples/links/sub1/inner1.md +11 -11
- data/spec/samples/links/sub2.md +4 -4
- data/spec/samples/links/sub2/inner2.md +7 -7
- data/spec/samples/simplest/HTML.html +9 -9
- data/spec/samples/simplest/Haml.haml +12 -12
- data/spec/samples/simplest/Markdown.md +9 -9
- data/spec/samples/simplest/Text.txt +9 -9
- data/spec/samples/simplest/Textile.textile +9 -9
- data/spec/samples/templates/_templates/doubler.haml +6 -6
- data/spec/samples/templates/_templates/page.haml +1 -1
- data/spec/samples/templates/goaway.txt +2 -2
- data/spec/samples/templates/onepara_html.html +2 -2
- data/spec/samples/templates/onepara_md.md +4 -4
- data/spec/samples/templates/twopara_haml.haml +6 -6
- data/spec/samples/templates/twopara_textile.textile +5 -5
- data/spec/samples/titles/3_renamed.txt +1 -1
- data/spec/samples/titles/index.txt +1 -1
- data/spec/templates.rb +43 -43
- data/spec/toc.rb +128 -128
- data/spec/writer/chm.rb +2 -2
- data/spec/writer/html.rb +2 -2
- metadata +56 -25
- data/bin/docubot.bat +0 -1
data/bin/docubot
CHANGED
@@ -1,125 +1,125 @@
|
|
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' )
|
6
|
-
|
7
|
-
require 'rubygems'
|
8
|
-
require 'docubot'
|
9
|
-
|
10
|
-
USAGE = <<ENDUSAGE
|
11
|
-
Usage:
|
12
|
-
docubot [-h] [create [-s shell] [-f]] directory [-w writer] [-o output_file] [-n] [-l log_file]
|
13
|
-
ENDUSAGE
|
14
|
-
|
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
|
-
-n, --nopreview Disable automatic preview of .chm.
|
28
|
-
-l, --logfile Specify the filename to log to.
|
29
|
-
|
30
|
-
ENDHELP
|
31
|
-
|
32
|
-
ARGS = { :shell=>'default', :writer=>'chm' }
|
33
|
-
UNFLAGGED_ARGS = [ :directory ]
|
34
|
-
next_arg = UNFLAGGED_ARGS.first
|
35
|
-
ARGV.each{ |arg|
|
36
|
-
case arg
|
37
|
-
when '-h','--help'
|
38
|
-
ARGS[:help] = true
|
39
|
-
when 'create'
|
40
|
-
ARGS[:create] = true
|
41
|
-
when '-f','--force'
|
42
|
-
ARGS[:force] = true
|
43
|
-
when '-s','--shell'
|
44
|
-
next_arg = :shell
|
45
|
-
when '-w','--writer'
|
46
|
-
next_arg = :writer
|
47
|
-
when '-o','--output'
|
48
|
-
next_arg = :output
|
49
|
-
when '-n','--nopreview'
|
50
|
-
ARGS[:nopreview] = true
|
51
|
-
when '-l','--logfile'
|
52
|
-
next_arg = :logfile
|
53
|
-
else
|
54
|
-
if next_arg
|
55
|
-
ARGS[next_arg] = arg
|
56
|
-
UNFLAGGED_ARGS.delete( next_arg )
|
57
|
-
end
|
58
|
-
next_arg = UNFLAGGED_ARGS.first
|
59
|
-
end
|
60
|
-
}
|
61
|
-
|
62
|
-
if ARGS[:help] or !ARGS[:directory]
|
63
|
-
puts USAGE
|
64
|
-
puts HELP if ARGS[:help]
|
65
|
-
exit
|
66
|
-
end
|
67
|
-
|
68
|
-
if ARGS[:logfile]
|
69
|
-
$stdout.reopen( ARGS[:logfile], "w" )
|
70
|
-
$stdout.sync = true
|
71
|
-
$stderr.reopen( $stdout )
|
72
|
-
end
|
73
|
-
|
74
|
-
if ARGS[:create]
|
75
|
-
require 'fileutils'
|
76
|
-
|
77
|
-
unless DocuBot::SHELLS.include?( ARGS[:shell] )
|
78
|
-
puts " Error: '#{ARGS[:shell]}' is not a valid shell.",
|
79
|
-
" Available shells: #{DocuBot::SHELLS.join(', ')}",
|
80
|
-
" (Shells are installed in #{DocuBot::DIR/'shells'})", ""
|
81
|
-
exit 1
|
82
|
-
end
|
83
|
-
|
84
|
-
if File.exist?( ARGS[:directory] )
|
85
|
-
if ARGS[:force]
|
86
|
-
# TODO: confirmation?
|
87
|
-
# TODO: May be able to just use :force=>true for cp_r
|
88
|
-
FileUtils.rm_rf( ARGS[:directory] )
|
89
|
-
else
|
90
|
-
puts " Error: directory '#{ARGS[:directory]}' already exists.",
|
91
|
-
" Use the --force option to forcibly overwrite.", ""
|
92
|
-
exit 1
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
dest = File.expand_path( ARGS[:directory] )
|
97
|
-
src = File.expand_path( DocuBot::SHELL_DIR/ARGS[:shell] )
|
98
|
-
puts " Creating: #{dest}",
|
99
|
-
" as copy of: #{src}", ""
|
100
|
-
|
101
|
-
# Copy template files first so that the shell can overwrite if it wants
|
102
|
-
FileUtils.mkdir_p( dest )
|
103
|
-
FileUtils.cp_r( DocuBot::TEMPLATE_DIR, dest/'_templates' )
|
104
|
-
|
105
|
-
Dir.chdir src do
|
106
|
-
Dir['**/*.*'].each do |file|
|
107
|
-
dest_file = dest/file
|
108
|
-
dest_dir = File.dirname(dest_file)
|
109
|
-
FileUtils.mkdir_p( dest_dir )
|
110
|
-
FileUtils.cp( file, dest_file )
|
111
|
-
end
|
112
|
-
end
|
113
|
-
else
|
114
|
-
# require 'perftools'
|
115
|
-
start = Time.now
|
116
|
-
# bundle = nil
|
117
|
-
# PerfTools::CpuProfiler.start("/tmp/docubot") do
|
118
|
-
bundle = DocuBot::Bundle.new( ARGS[:directory] )
|
119
|
-
# end
|
120
|
-
lap = Time.now
|
121
|
-
puts "%.2fs to prepare the bundle..." % (lap-start)
|
122
|
-
bundle.write( ARGS[:writer], ARGS[:output] )
|
123
|
-
puts "%.2fs to write everything." % (Time.now-lap)
|
124
|
-
end
|
125
|
-
|
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' )
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'docubot'
|
9
|
+
|
10
|
+
USAGE = <<ENDUSAGE
|
11
|
+
Usage:
|
12
|
+
docubot [-h] [create [-s shell] [-f]] directory [-w writer] [-o output_file] [-n] [-l log_file]
|
13
|
+
ENDUSAGE
|
14
|
+
|
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
|
+
-n, --nopreview Disable automatic preview of .chm.
|
28
|
+
-l, --logfile Specify the filename to log to.
|
29
|
+
|
30
|
+
ENDHELP
|
31
|
+
|
32
|
+
ARGS = { :shell=>'default', :writer=>'chm' }
|
33
|
+
UNFLAGGED_ARGS = [ :directory ]
|
34
|
+
next_arg = UNFLAGGED_ARGS.first
|
35
|
+
ARGV.each{ |arg|
|
36
|
+
case arg
|
37
|
+
when '-h','--help'
|
38
|
+
ARGS[:help] = true
|
39
|
+
when 'create'
|
40
|
+
ARGS[:create] = true
|
41
|
+
when '-f','--force'
|
42
|
+
ARGS[:force] = true
|
43
|
+
when '-s','--shell'
|
44
|
+
next_arg = :shell
|
45
|
+
when '-w','--writer'
|
46
|
+
next_arg = :writer
|
47
|
+
when '-o','--output'
|
48
|
+
next_arg = :output
|
49
|
+
when '-n','--nopreview'
|
50
|
+
ARGS[:nopreview] = true
|
51
|
+
when '-l','--logfile'
|
52
|
+
next_arg = :logfile
|
53
|
+
else
|
54
|
+
if next_arg
|
55
|
+
ARGS[next_arg] = arg
|
56
|
+
UNFLAGGED_ARGS.delete( next_arg )
|
57
|
+
end
|
58
|
+
next_arg = UNFLAGGED_ARGS.first
|
59
|
+
end
|
60
|
+
}
|
61
|
+
|
62
|
+
if ARGS[:help] or !ARGS[:directory]
|
63
|
+
puts USAGE
|
64
|
+
puts HELP if ARGS[:help]
|
65
|
+
exit
|
66
|
+
end
|
67
|
+
|
68
|
+
if ARGS[:logfile]
|
69
|
+
$stdout.reopen( ARGS[:logfile], "w" )
|
70
|
+
$stdout.sync = true
|
71
|
+
$stderr.reopen( $stdout )
|
72
|
+
end
|
73
|
+
|
74
|
+
if ARGS[:create]
|
75
|
+
require 'fileutils'
|
76
|
+
|
77
|
+
unless DocuBot::SHELLS.include?( ARGS[:shell] )
|
78
|
+
puts " Error: '#{ARGS[:shell]}' is not a valid shell.",
|
79
|
+
" Available shells: #{DocuBot::SHELLS.join(', ')}",
|
80
|
+
" (Shells are installed in #{DocuBot::DIR/'shells'})", ""
|
81
|
+
exit 1
|
82
|
+
end
|
83
|
+
|
84
|
+
if File.exist?( ARGS[:directory] )
|
85
|
+
if ARGS[:force]
|
86
|
+
# TODO: confirmation?
|
87
|
+
# TODO: May be able to just use :force=>true for cp_r
|
88
|
+
FileUtils.rm_rf( ARGS[:directory] )
|
89
|
+
else
|
90
|
+
puts " Error: directory '#{ARGS[:directory]}' already exists.",
|
91
|
+
" Use the --force option to forcibly overwrite.", ""
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
dest = File.expand_path( ARGS[:directory] )
|
97
|
+
src = File.expand_path( DocuBot::SHELL_DIR/ARGS[:shell] )
|
98
|
+
puts " Creating: #{dest}",
|
99
|
+
" as copy of: #{src}", ""
|
100
|
+
|
101
|
+
# Copy template files first so that the shell can overwrite if it wants
|
102
|
+
FileUtils.mkdir_p( dest )
|
103
|
+
FileUtils.cp_r( DocuBot::TEMPLATE_DIR, dest/'_templates' )
|
104
|
+
|
105
|
+
Dir.chdir src do
|
106
|
+
Dir['**/*.*'].each do |file|
|
107
|
+
dest_file = dest/file
|
108
|
+
dest_dir = File.dirname(dest_file)
|
109
|
+
FileUtils.mkdir_p( dest_dir )
|
110
|
+
FileUtils.cp( file, dest_file )
|
111
|
+
end
|
112
|
+
end
|
113
|
+
else
|
114
|
+
# require 'perftools'
|
115
|
+
start = Time.now
|
116
|
+
# bundle = nil
|
117
|
+
# PerfTools::CpuProfiler.start("/tmp/docubot") do
|
118
|
+
bundle = DocuBot::Bundle.new( ARGS[:directory] )
|
119
|
+
# end
|
120
|
+
lap = Time.now
|
121
|
+
puts "%.2fs to prepare the bundle..." % (lap-start)
|
122
|
+
bundle.write( ARGS[:writer], ARGS[:output] )
|
123
|
+
puts "%.2fs to write everything." % (Time.now-lap)
|
124
|
+
end
|
125
|
+
|
data/lib/docubot.rb
CHANGED
@@ -1,43 +1,43 @@
|
|
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( '/', '\\' )
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
module DocuBot
|
23
|
-
VERSION = '0.6.
|
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
|
-
|
30
|
-
def self.id_from_text( text )
|
31
|
-
"#" << text.strip.gsub(/[^\w.:-]+/,'-').gsub(/^[^a-z]+|-+$/i,'')
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
require 'docubot/link_tree'
|
36
|
-
require 'docubot/metasection'
|
37
|
-
require 'docubot/snippet'
|
38
|
-
require 'docubot/converter'
|
39
|
-
require 'docubot/writer'
|
40
|
-
require 'docubot/page'
|
41
|
-
require 'docubot/glossary'
|
42
|
-
require 'docubot/index'
|
43
|
-
require 'docubot/bundle'
|
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( '/', '\\' )
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module DocuBot
|
23
|
+
VERSION = '0.6.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
|
+
|
30
|
+
def self.id_from_text( text )
|
31
|
+
"#" << text.strip.gsub(/[^\w.:-]+/,'-').gsub(/^[^a-z]+|-+$/i,'')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'docubot/link_tree'
|
36
|
+
require 'docubot/metasection'
|
37
|
+
require 'docubot/snippet'
|
38
|
+
require 'docubot/converter'
|
39
|
+
require 'docubot/writer'
|
40
|
+
require 'docubot/page'
|
41
|
+
require 'docubot/glossary'
|
42
|
+
require 'docubot/index'
|
43
|
+
require 'docubot/bundle'
|
data/lib/docubot/bundle.rb
CHANGED
@@ -1,182 +1,182 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
require 'pathname'
|
3
|
-
class DocuBot::Bundle
|
4
|
-
attr_reader :toc, :extras, :glossary, :index, :source, :global
|
5
|
-
attr_reader :internal_links, :external_links, :file_links, :broken_links
|
6
|
-
attr_reader :pages, :pages_by_title, :page_by_file_path, :page_by_html_path
|
7
|
-
def initialize( source_directory )
|
8
|
-
@source = File.expand_path( source_directory )
|
9
|
-
raise "DocuBot cannot find directory #{@source}. Exiting." unless File.exists?( @source )
|
10
|
-
@pages = []
|
11
|
-
@extras = []
|
12
|
-
@pages_by_title = Hash.new{ |h,k| h[k]=[] }
|
13
|
-
@page_by_file_path = {}
|
14
|
-
@page_by_html_path = {}
|
15
|
-
|
16
|
-
@glossary = DocuBot::Glossary.new( self, @source/'_glossary' )
|
17
|
-
@index = DocuBot::Index.new( self )
|
18
|
-
@toc = DocuBot::LinkTree::Root.new( self )
|
19
|
-
|
20
|
-
Dir.chdir( @source ) do
|
21
|
-
# This might be nil; MetaSection.new is OK with that.
|
22
|
-
index_file = Dir[ *DocuBot::Converter.types.map{|t| "index.#{t}"} ][ 0 ]
|
23
|
-
@global = DocuBot::MetaSection.new( {:title=>'DocuBot Documentation'}, index_file )
|
24
|
-
@global.glossary = @glossary
|
25
|
-
@global.index = @index
|
26
|
-
@global.toc = @toc
|
27
|
-
|
28
|
-
files_and_folders = Dir[ '**/*' ]
|
29
|
-
|
30
|
-
# index files are handled by Page.new for a directory; no sections for special folders (but process contents)
|
31
|
-
files_and_folders.reject!{ |path| name = File.basename( path ); name =~ /^(?:index\.[^.]+)$/ }
|
32
|
-
|
33
|
-
# All files in the _templates directory should be ignored
|
34
|
-
files_and_folders.reject!{ |f| f =~ /(?:^|\/)_/ }
|
35
|
-
files_and_folders.concat Dir[ '_static/**/*' ]
|
36
|
-
files_and_folders.concat Dir[ '_glossary/**/*' ]
|
37
|
-
|
38
|
-
@global.ignore.as_list.each do |glob|
|
39
|
-
files_and_folders = files_and_folders - Dir[glob]
|
40
|
-
end
|
41
|
-
|
42
|
-
create_pages( files_and_folders )
|
43
|
-
end
|
44
|
-
# puts @toc.to_txt
|
45
|
-
|
46
|
-
# Regenerate pages whose templates require full scaning to have completed
|
47
|
-
# TODO: make this based off of a metasection attribute.
|
48
|
-
@pages.select do |page|
|
49
|
-
%w[ glossary ].include?( page.template )
|
50
|
-
end.each do |page|
|
51
|
-
page.dirty_template
|
52
|
-
end
|
53
|
-
|
54
|
-
# TODO: make this optional via global variable
|
55
|
-
validate_links
|
56
|
-
warn_for_broken_links
|
57
|
-
|
58
|
-
# TODO: make this optional via global variable
|
59
|
-
warn_for_missing_glossary_terms
|
60
|
-
|
61
|
-
find_page_collisions
|
62
|
-
end
|
63
|
-
|
64
|
-
def create_pages( files_and_folders )
|
65
|
-
files_and_folders.each do |path|
|
66
|
-
extension = File.extname( path )[ 1..-1 ]
|
67
|
-
item_is_page = File.directory?(path) || DocuBot::Converter.by_type[extension]
|
68
|
-
if !item_is_page
|
69
|
-
@extras << path
|
70
|
-
else
|
71
|
-
page = DocuBot::Page.new( self, path )
|
72
|
-
|
73
|
-
if path =~ %r{^_glossary/}
|
74
|
-
@glossary << page
|
75
|
-
else
|
76
|
-
@pages << page
|
77
|
-
@page_by_file_path[path] = page
|
78
|
-
@page_by_html_path[page.html_path] = page
|
79
|
-
@pages_by_title[page.title] << page
|
80
|
-
@index.process_page( page )
|
81
|
-
|
82
|
-
# Add the page (and any sub-links) to the toc
|
83
|
-
unless page.hide
|
84
|
-
@toc.add_to_link_hierarchy( page.title, page.html_path, page )
|
85
|
-
page.toc.as_list.each do |id_or_text|
|
86
|
-
id = id_or_text[0..0] == '#' ? id_or_text : DocuBot.id_from_text(id_or_text)
|
87
|
-
if ele = page.nokodoc.at_css(id)
|
88
|
-
@toc.add_to_link_hierarchy( ele.inner_text, page.html_path + id, page )
|
89
|
-
else
|
90
|
-
warn "Could not find requested toc anchor #{id.inspect} based on #{id_or_text.inspect} on #{page.html_path}"
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
def validate_links
|
101
|
-
@external_links = Hash.new{ |h,k| h[k]=[] }
|
102
|
-
@internal_links = Hash.new{ |h,k| h[k]=[] }
|
103
|
-
@file_links = Hash.new{ |h,k| h[k]=[] }
|
104
|
-
@broken_links = Hash.new{ |h,k| h[k]=[] }
|
105
|
-
|
106
|
-
Dir.chdir( @source ) do
|
107
|
-
@pages.each do |page|
|
108
|
-
# TODO: set the xpath to .//a/@href once this is fixed: http://github.com/tenderlove/nokogiri/issues/#issue/213
|
109
|
-
page.nokodoc.xpath('.//a').each do |a|
|
110
|
-
next unless href = a['href']
|
111
|
-
href = CGI.unescape(href)
|
112
|
-
if href=~%r{^[a-z]+://}i
|
113
|
-
@external_links[page] << href
|
114
|
-
else
|
115
|
-
id = href[/#([a-z][\w.:-]*)?/i]
|
116
|
-
file = href.sub(/#.*/,'')
|
117
|
-
path = file.empty? ? page.html_path : Pathname.new( File.dirname(page.html_path) / file ).cleanpath.to_s
|
118
|
-
if target=@page_by_html_path[path]
|
119
|
-
if !id || id == "#" || target.nokodoc.at_css(id)
|
120
|
-
@internal_links[page] << href
|
121
|
-
else
|
122
|
-
warn "Could not find internal link for #{id.inspect} on #{page.html_path.inspect}" if id
|
123
|
-
@broken_links[page] << href
|
124
|
-
end
|
125
|
-
else
|
126
|
-
if File.file?(path) && !@page_by_file_path[path]
|
127
|
-
@file_links[page] << href
|
128
|
-
else
|
129
|
-
@broken_links[page] << href
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def warn_for_broken_links
|
139
|
-
@broken_links.each do |page,links|
|
140
|
-
links.each do |link|
|
141
|
-
warn "Broken link on #{page.file}: '#{link}'"
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def warn_for_missing_glossary_terms
|
147
|
-
@glossary.missing_terms.each do |term,referrers|
|
148
|
-
warn "Glossary term '#{term}' never defined."
|
149
|
-
referrers.each do |referring_page|
|
150
|
-
warn "...seen on #{referring_page.file}."
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
def find_page_collisions
|
156
|
-
# Find any and all pages that would collide
|
157
|
-
pages_by_html_path = Hash.new{ |h,k| h[k] = [] }
|
158
|
-
@pages.each do |page|
|
159
|
-
pages_by_html_path[page.html_path] << page
|
160
|
-
end
|
161
|
-
collisions = pages_by_html_path.select{ |path,pages| pages.length>1 }
|
162
|
-
unless collisions.empty?
|
163
|
-
message = collisions.map do |path,pages|
|
164
|
-
"#{path}: #{pages.map{ |page| "'#{page.title}' (#{page.file})" }.join(', ')}"
|
165
|
-
end.join("\n")
|
166
|
-
raise PageCollision.new, message
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def write( writer_type, destination=nil )
|
171
|
-
writer = DocuBot::Writer.by_type[ writer_type.to_s.downcase ]
|
172
|
-
if writer
|
173
|
-
writer.new( self ).write( destination )
|
174
|
-
else
|
175
|
-
raise "Unknown writer '#{writer_type}'; available types: #{DocuBot::Writer::INSTALLED_WRITERS.join ', '}"
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
end
|
180
|
-
|
181
|
-
class DocuBot::Bundle::PageCollision < RuntimeError; end
|
182
|
-
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'pathname'
|
3
|
+
class DocuBot::Bundle
|
4
|
+
attr_reader :toc, :extras, :glossary, :index, :source, :global
|
5
|
+
attr_reader :internal_links, :external_links, :file_links, :broken_links
|
6
|
+
attr_reader :pages, :pages_by_title, :page_by_file_path, :page_by_html_path
|
7
|
+
def initialize( source_directory )
|
8
|
+
@source = File.expand_path( source_directory )
|
9
|
+
raise "DocuBot cannot find directory #{@source}. Exiting." unless File.exists?( @source )
|
10
|
+
@pages = []
|
11
|
+
@extras = []
|
12
|
+
@pages_by_title = Hash.new{ |h,k| h[k]=[] }
|
13
|
+
@page_by_file_path = {}
|
14
|
+
@page_by_html_path = {}
|
15
|
+
|
16
|
+
@glossary = DocuBot::Glossary.new( self, @source/'_glossary' )
|
17
|
+
@index = DocuBot::Index.new( self )
|
18
|
+
@toc = DocuBot::LinkTree::Root.new( self )
|
19
|
+
|
20
|
+
Dir.chdir( @source ) do
|
21
|
+
# This might be nil; MetaSection.new is OK with that.
|
22
|
+
index_file = Dir[ *DocuBot::Converter.types.map{|t| "index.#{t}"} ][ 0 ]
|
23
|
+
@global = DocuBot::MetaSection.new( {:title=>'DocuBot Documentation'}, index_file )
|
24
|
+
@global.glossary = @glossary
|
25
|
+
@global.index = @index
|
26
|
+
@global.toc = @toc
|
27
|
+
|
28
|
+
files_and_folders = Dir[ '**/*' ]
|
29
|
+
|
30
|
+
# index files are handled by Page.new for a directory; no sections for special folders (but process contents)
|
31
|
+
files_and_folders.reject!{ |path| name = File.basename( path ); name =~ /^(?:index\.[^.]+)$/ }
|
32
|
+
|
33
|
+
# All files in the _templates directory should be ignored
|
34
|
+
files_and_folders.reject!{ |f| f =~ /(?:^|\/)_/ }
|
35
|
+
files_and_folders.concat Dir[ '_static/**/*' ]
|
36
|
+
files_and_folders.concat Dir[ '_glossary/**/*' ]
|
37
|
+
|
38
|
+
@global.ignore.as_list.each do |glob|
|
39
|
+
files_and_folders = files_and_folders - Dir[glob]
|
40
|
+
end
|
41
|
+
|
42
|
+
create_pages( files_and_folders )
|
43
|
+
end
|
44
|
+
# puts @toc.to_txt
|
45
|
+
|
46
|
+
# Regenerate pages whose templates require full scaning to have completed
|
47
|
+
# TODO: make this based off of a metasection attribute.
|
48
|
+
@pages.select do |page|
|
49
|
+
%w[ glossary ].include?( page.template )
|
50
|
+
end.each do |page|
|
51
|
+
page.dirty_template
|
52
|
+
end
|
53
|
+
|
54
|
+
# TODO: make this optional via global variable
|
55
|
+
validate_links
|
56
|
+
warn_for_broken_links
|
57
|
+
|
58
|
+
# TODO: make this optional via global variable
|
59
|
+
warn_for_missing_glossary_terms
|
60
|
+
|
61
|
+
find_page_collisions
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_pages( files_and_folders )
|
65
|
+
files_and_folders.each do |path|
|
66
|
+
extension = File.extname( path )[ 1..-1 ]
|
67
|
+
item_is_page = File.directory?(path) || DocuBot::Converter.by_type[extension]
|
68
|
+
if !item_is_page
|
69
|
+
@extras << path
|
70
|
+
else
|
71
|
+
page = DocuBot::Page.new( self, path )
|
72
|
+
|
73
|
+
if path =~ %r{^_glossary/}
|
74
|
+
@glossary << page
|
75
|
+
else
|
76
|
+
@pages << page
|
77
|
+
@page_by_file_path[path] = page
|
78
|
+
@page_by_html_path[page.html_path] = page
|
79
|
+
@pages_by_title[page.title] << page
|
80
|
+
@index.process_page( page )
|
81
|
+
|
82
|
+
# Add the page (and any sub-links) to the toc
|
83
|
+
unless page.hide
|
84
|
+
@toc.add_to_link_hierarchy( page.title, page.html_path, page )
|
85
|
+
page.toc.as_list.each do |id_or_text|
|
86
|
+
id = id_or_text[0..0] == '#' ? id_or_text : DocuBot.id_from_text(id_or_text)
|
87
|
+
if ele = page.nokodoc.at_css(id)
|
88
|
+
@toc.add_to_link_hierarchy( ele.inner_text, page.html_path + id, page )
|
89
|
+
else
|
90
|
+
warn "Could not find requested toc anchor #{id.inspect} based on #{id_or_text.inspect} on #{page.html_path}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def validate_links
|
101
|
+
@external_links = Hash.new{ |h,k| h[k]=[] }
|
102
|
+
@internal_links = Hash.new{ |h,k| h[k]=[] }
|
103
|
+
@file_links = Hash.new{ |h,k| h[k]=[] }
|
104
|
+
@broken_links = Hash.new{ |h,k| h[k]=[] }
|
105
|
+
|
106
|
+
Dir.chdir( @source ) do
|
107
|
+
@pages.each do |page|
|
108
|
+
# TODO: set the xpath to .//a/@href once this is fixed: http://github.com/tenderlove/nokogiri/issues/#issue/213
|
109
|
+
page.nokodoc.xpath('.//a').each do |a|
|
110
|
+
next unless href = a['href']
|
111
|
+
href = CGI.unescape(href)
|
112
|
+
if href=~%r{^[a-z]+://}i
|
113
|
+
@external_links[page] << href
|
114
|
+
else
|
115
|
+
id = href[/#([a-z][\w.:-]*)?/i]
|
116
|
+
file = href.sub(/#.*/,'')
|
117
|
+
path = file.empty? ? page.html_path : Pathname.new( File.dirname(page.html_path) / file ).cleanpath.to_s
|
118
|
+
if target=@page_by_html_path[path]
|
119
|
+
if !id || id == "#" || target.nokodoc.at_css(id)
|
120
|
+
@internal_links[page] << href
|
121
|
+
else
|
122
|
+
warn "Could not find internal link for #{id.inspect} on #{page.html_path.inspect}" if id
|
123
|
+
@broken_links[page] << href
|
124
|
+
end
|
125
|
+
else
|
126
|
+
if File.file?(path) && !@page_by_file_path[path]
|
127
|
+
@file_links[page] << href
|
128
|
+
else
|
129
|
+
@broken_links[page] << href
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def warn_for_broken_links
|
139
|
+
@broken_links.each do |page,links|
|
140
|
+
links.each do |link|
|
141
|
+
warn "Broken link on #{page.file}: '#{link}'"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def warn_for_missing_glossary_terms
|
147
|
+
@glossary.missing_terms.each do |term,referrers|
|
148
|
+
warn "Glossary term '#{term}' never defined."
|
149
|
+
referrers.each do |referring_page|
|
150
|
+
warn "...seen on #{referring_page.file}."
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def find_page_collisions
|
156
|
+
# Find any and all pages that would collide
|
157
|
+
pages_by_html_path = Hash.new{ |h,k| h[k] = [] }
|
158
|
+
@pages.each do |page|
|
159
|
+
pages_by_html_path[page.html_path] << page
|
160
|
+
end
|
161
|
+
collisions = pages_by_html_path.select{ |path,pages| pages.length>1 }
|
162
|
+
unless collisions.empty?
|
163
|
+
message = collisions.map do |path,pages|
|
164
|
+
"#{path}: #{pages.map{ |page| "'#{page.title}' (#{page.file})" }.join(', ')}"
|
165
|
+
end.join("\n")
|
166
|
+
raise PageCollision.new, message
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def write( writer_type, destination=nil )
|
171
|
+
writer = DocuBot::Writer.by_type[ writer_type.to_s.downcase ]
|
172
|
+
if writer
|
173
|
+
writer.new( self ).write( destination )
|
174
|
+
else
|
175
|
+
raise "Unknown writer '#{writer_type}'; available types: #{DocuBot::Writer::INSTALLED_WRITERS.join ', '}"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
|
181
|
+
class DocuBot::Bundle::PageCollision < RuntimeError; end
|
182
|
+
|