docubot 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|
+
|