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