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