docubot 0.0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
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