docubot 0.6.1 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/bin/docubot +125 -125
  2. data/lib/docubot.rb +43 -43
  3. data/lib/docubot/bundle.rb +182 -182
  4. data/lib/docubot/converter.rb +28 -28
  5. data/lib/docubot/converters/haml.rb +9 -9
  6. data/lib/docubot/converters/markdown.rb +14 -14
  7. data/lib/docubot/index.rb +67 -67
  8. data/lib/docubot/link_tree.rb +111 -111
  9. data/lib/docubot/metasection.rb +61 -61
  10. data/lib/docubot/page.rb +167 -167
  11. data/lib/docubot/shells/default/0_License.md +59 -59
  12. data/lib/docubot/shells/default/Appendix/Glossary.md +4 -4
  13. data/lib/docubot/shells/default/Appendix/Index_Page.md +8 -8
  14. data/lib/docubot/shells/default/Appendix/Table of Contents.md +4 -4
  15. data/lib/docubot/shells/docubot-help/0_License.md +20 -20
  16. data/lib/docubot/shells/docubot-help/1_Getting_Started.md +47 -47
  17. data/lib/docubot/shells/docubot-help/2_Basic_Concepts/4 Adding Images.md +1 -1
  18. data/lib/docubot/shells/docubot-help/2_Basic_Concepts/index.md +5 -5
  19. data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling Glossary.md +2 -2
  20. data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling Indexing.md +9 -9
  21. data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling the Table of Contents.md +5 -5
  22. data/lib/docubot/shells/docubot-help/4_Appendix/Glossary.md +4 -4
  23. data/lib/docubot/shells/docubot-help/4_Appendix/Index_Page.md +5 -5
  24. data/lib/docubot/shells/docubot-help/4_Appendix/Table of Contents.md +7 -7
  25. data/lib/docubot/shells/docubot-help/index.txt +7 -7
  26. data/lib/docubot/snippet.rb +19 -19
  27. data/lib/docubot/snippets/glossary.rb +7 -7
  28. data/lib/docubot/snippets/index_entries.rb +6 -6
  29. data/lib/docubot/templates/_root/glossary.css +3 -3
  30. data/lib/docubot/templates/_root/glossary.js +57 -57
  31. data/lib/docubot/templates/index.haml +14 -14
  32. data/lib/docubot/templates/toc.haml +2 -2
  33. data/lib/docubot/templates/top.haml +32 -32
  34. data/lib/docubot/writers/chm/hhc.erb +1 -1
  35. data/lib/docubot/writers/chm/hhk.erb +2 -2
  36. data/lib/docubot/writers/html.rb +87 -87
  37. data/spec/_all.rb +12 -12
  38. data/spec/_helper.rb +16 -16
  39. data/spec/bundle.rb +339 -339
  40. data/spec/command.rb +3 -3
  41. data/spec/converters.rb +2 -2
  42. data/spec/global.rb +27 -27
  43. data/spec/glossary.rb +94 -94
  44. data/spec/index.rb +2 -2
  45. data/spec/page.rb +80 -80
  46. data/spec/samples/attributes/defaults.haml +34 -34
  47. data/spec/samples/attributes/explicit1.haml +42 -42
  48. data/spec/samples/attributes/explicit2.haml +41 -41
  49. data/spec/samples/attributes/hidden.haml +39 -39
  50. data/spec/samples/attributes/index.md +8 -8
  51. data/spec/samples/collisions/page1.md +2 -2
  52. data/spec/samples/collisions/page1.textile +2 -2
  53. data/spec/samples/collisions/page2.haml +3 -3
  54. data/spec/samples/collisions/page2.html +2 -2
  55. data/spec/samples/collisions/page2.txt +2 -2
  56. data/spec/samples/collisions/page3.md +2 -2
  57. data/spec/samples/files/index.md +1 -1
  58. data/spec/samples/glossary/Glossary.txt +4 -4
  59. data/spec/samples/glossary/Some Page.md +2 -2
  60. data/spec/samples/glossary/_glossary/Simple Term.md +2 -2
  61. data/spec/samples/glossary/_glossary/complex.haml +8 -8
  62. data/spec/samples/glossary/_glossary/project_x.md +3 -3
  63. data/spec/samples/hierarchy/1/1.1/1.1.1/index.haml +1 -1
  64. data/spec/samples/hierarchy/1/1.1/1.1.1/page.haml +3 -3
  65. data/spec/samples/hierarchy/1/1.1/page.haml +3 -3
  66. data/spec/samples/hierarchy/1/page.haml +3 -3
  67. data/spec/samples/hierarchy/2/2.1/2.1.1/page.haml +3 -3
  68. data/spec/samples/hierarchy/2/2.1/index.haml +1 -1
  69. data/spec/samples/hierarchy/2/2.1/page.haml +3 -3
  70. data/spec/samples/hierarchy/2/page.haml +3 -3
  71. data/spec/samples/hierarchy/main.haml +1 -1
  72. data/spec/samples/hierarchy/toc.md +1 -1
  73. data/spec/samples/links/index.txt +11 -11
  74. data/spec/samples/links/root.md +13 -13
  75. data/spec/samples/links/sub1/inner1.md +11 -11
  76. data/spec/samples/links/sub2.md +4 -4
  77. data/spec/samples/links/sub2/inner2.md +7 -7
  78. data/spec/samples/simplest/HTML.html +9 -9
  79. data/spec/samples/simplest/Haml.haml +12 -12
  80. data/spec/samples/simplest/Markdown.md +9 -9
  81. data/spec/samples/simplest/Text.txt +9 -9
  82. data/spec/samples/simplest/Textile.textile +9 -9
  83. data/spec/samples/templates/_templates/doubler.haml +6 -6
  84. data/spec/samples/templates/_templates/page.haml +1 -1
  85. data/spec/samples/templates/goaway.txt +2 -2
  86. data/spec/samples/templates/onepara_html.html +2 -2
  87. data/spec/samples/templates/onepara_md.md +4 -4
  88. data/spec/samples/templates/twopara_haml.haml +6 -6
  89. data/spec/samples/templates/twopara_textile.textile +5 -5
  90. data/spec/samples/titles/3_renamed.txt +1 -1
  91. data/spec/samples/titles/index.txt +1 -1
  92. data/spec/templates.rb +43 -43
  93. data/spec/toc.rb +128 -128
  94. data/spec/writer/chm.rb +2 -2
  95. data/spec/writer/html.rb +2 -2
  96. metadata +56 -25
  97. data/bin/docubot.bat +0 -1
@@ -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
+
@@ -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.1'
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'
@@ -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
+