docubot 0.3.3 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/docubot +5 -1
- data/lib/docubot.rb +4 -2
- data/lib/docubot/bundle.rb +109 -61
- data/lib/docubot/converter.rb +4 -1
- data/lib/docubot/converters/haml.rb +1 -1
- data/lib/docubot/glossary.rb +3 -0
- data/lib/docubot/link_tree.rb +109 -0
- data/lib/docubot/metasection.rb +61 -0
- data/lib/docubot/page.rb +40 -118
- data/lib/docubot/shells/docubot-help/3_Advanced_Topics/Controlling the Table of Contents.md +2 -4
- data/lib/docubot/shells/nvphysx/_templates/_root/common.css +10 -5
- data/lib/docubot/shells/nvphysx/_templates/_root/glossary.css +1 -0
- data/lib/docubot/shells/nvphysx/_templates/_root/glossary.js +11 -1
- data/lib/docubot/shells/nvphysx/_templates/section.haml +9 -0
- data/lib/docubot/templates/index.haml +13 -20
- data/lib/docubot/templates/section.haml +1 -4
- data/lib/docubot/templates/toc.haml +2 -11
- data/lib/docubot/templates/top.haml +7 -4
- data/lib/docubot/writers/chm.rb +6 -5
- data/lib/docubot/writers/chm/hhc.erb +35 -12
- data/lib/docubot/writers/chm/hhk.erb +1 -1
- data/lib/docubot/writers/chm/hhp.erb +2 -2
- data/lib/docubot/writers/html.rb +33 -23
- data/spec/_all.rb +1 -0
- data/spec/_helper.rb +10 -0
- data/spec/bundle.rb +193 -2
- data/spec/global.rb +28 -0
- data/spec/glossary.rb +13 -11
- data/spec/page.rb +35 -9
- data/spec/samples/attributes/defaults.haml +34 -0
- data/spec/samples/attributes/explicit1.haml +43 -0
- data/spec/samples/attributes/explicit2.haml +42 -0
- data/spec/samples/attributes/hidden.haml +40 -0
- data/spec/samples/attributes/index.md +8 -0
- data/spec/samples/collisions/page1.md +3 -0
- data/spec/samples/collisions/page1.textile +3 -0
- data/spec/samples/collisions/page2.haml +4 -0
- data/spec/samples/collisions/page2.html +3 -0
- data/spec/samples/collisions/page2.txt +3 -0
- data/spec/samples/collisions/page3.bin +1 -0
- data/spec/samples/collisions/page3.md +3 -0
- data/spec/samples/{link_test/sub2/bozo.bin → files/BUILDING.txt} +0 -0
- data/spec/samples/{titletest/1 First One.txt → files/_static/Thumbs.db} +0 -0
- data/spec/samples/{titletest/2_Second_One.txt → files/_static/foo.ai} +0 -0
- data/spec/samples/{titletest/4 Fourth_One.txt → files/_static/foo.png} +0 -0
- data/spec/samples/{titletest/5_Fifth One.txt → files/_static/foo.psd} +0 -0
- data/spec/samples/files/another.md +0 -0
- data/spec/samples/files/common.css +0 -0
- data/spec/samples/files/first.textile +0 -0
- data/spec/samples/files/index.md +2 -0
- data/spec/samples/files/section/foo.jpg +0 -0
- data/spec/samples/files/section/page.haml +0 -0
- data/spec/samples/files/section/sub section/Thumbs.db b/data/spec/samples/files/section/sub → section/Thumbs.db +0 -0
- data/spec/samples/files/section/sub section/foo.gif b/data/spec/samples/files/section/sub → section/foo.gif +0 -0
- data/spec/samples/files/section/sub section/page.txt b/data/spec/samples/files/section/sub → section/page.txt +0 -0
- data/spec/samples/{link_test → links}/index.txt +0 -0
- data/spec/samples/{link_test → links}/root.md +0 -0
- data/spec/samples/{link_test → links}/sub1/inner1.md +0 -0
- data/spec/samples/{link_test → links}/sub2.md +0 -0
- data/spec/samples/links/sub2/bozo.bin +0 -0
- data/spec/samples/{link_test → links}/sub2/inner2.md +0 -0
- data/spec/samples/templates/_templates/404.haml +1 -0
- data/spec/samples/templates/_templates/doubler.haml +7 -0
- data/spec/samples/templates/_templates/page.haml +2 -0
- data/spec/samples/templates/goaway.txt +3 -0
- data/spec/samples/templates/onepara_html.html +3 -0
- data/spec/samples/templates/onepara_md.md +5 -0
- data/spec/samples/templates/twopara_haml.haml +7 -0
- data/spec/samples/templates/twopara_textile.textile +6 -0
- data/spec/samples/titles/1 First One.txt b/data/spec/samples/titles/1 First → One.txt +0 -0
- data/spec/samples/titles/2_Second_One.txt +0 -0
- data/spec/samples/{titletest → titles}/3_renamed.txt +0 -0
- data/spec/samples/titles/4 Fourth_One.txt b/data/spec/samples/titles/4 → Fourth_One.txt +0 -0
- data/spec/samples/titles/5_Fifth One.txt b/data/spec/samples/titles/5_Fifth → One.txt +0 -0
- data/spec/samples/titles/911.txt +0 -0
- data/spec/samples/titles/index.txt +2 -0
- data/spec/templates.rb +42 -0
- data/spec/toc.rb +64 -30
- metadata +53 -14
- data/spec/samples/titletest/index.txt +0 -2
data/bin/docubot
CHANGED
@@ -99,8 +99,12 @@ if ARGS[:create]
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
else
|
102
|
+
# require 'perftools'
|
102
103
|
start = Time.now
|
103
|
-
bundle =
|
104
|
+
# bundle = nil
|
105
|
+
# PerfTools::CpuProfiler.start("/tmp/docubot") do
|
106
|
+
bundle = DocuBot::Bundle.new( ARGS[:directory] )
|
107
|
+
# end
|
104
108
|
lap = Time.now
|
105
109
|
puts "%.2fs to prepare the bundle..." % (lap-start)
|
106
110
|
bundle.write( ARGS[:writer], ARGS[:output] )
|
data/lib/docubot.rb
CHANGED
@@ -20,7 +20,7 @@ module FileUtils
|
|
20
20
|
end
|
21
21
|
|
22
22
|
module DocuBot
|
23
|
-
VERSION = '0.
|
23
|
+
VERSION = '0.4'
|
24
24
|
DIR = File.expand_path( File.dirname( __FILE__ ) )
|
25
25
|
|
26
26
|
TEMPLATE_DIR = DIR / 'docubot/templates'
|
@@ -28,10 +28,12 @@ module DocuBot
|
|
28
28
|
Dir.chdir( SHELL_DIR ){ SHELLS = Dir['*'] }
|
29
29
|
|
30
30
|
def self.id_from_text( text )
|
31
|
-
text.strip.gsub(/[^\w.:-]+/,'-').gsub(
|
31
|
+
"#" << text.strip.gsub(/[^\w.:-]+/,'-').gsub(/^[^a-z]+|-+$/i,'')
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
require 'docubot/link_tree'
|
36
|
+
require 'docubot/metasection'
|
35
37
|
require 'docubot/snippet'
|
36
38
|
require 'docubot/converter'
|
37
39
|
require 'docubot/writer'
|
data/lib/docubot/bundle.rb
CHANGED
@@ -1,64 +1,49 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'pathname'
|
3
3
|
class DocuBot::Bundle
|
4
|
-
attr_reader :toc, :extras, :glossary, :index, :source
|
4
|
+
attr_reader :toc, :extras, :glossary, :index, :source, :global
|
5
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
|
6
7
|
def initialize( source_directory )
|
7
8
|
@source = File.expand_path( source_directory )
|
8
9
|
raise "DocuBot cannot find directory #{@source}. Exiting." unless File.exists?( @source )
|
10
|
+
@pages = []
|
9
11
|
@extras = []
|
12
|
+
@pages_by_title = Hash.new{ |h,k| h[k]=[] }
|
13
|
+
@page_by_file_path = {}
|
14
|
+
@page_by_html_path = {}
|
15
|
+
|
10
16
|
@glossary = DocuBot::Glossary.new( self, @source/'_glossary' )
|
11
17
|
@index = DocuBot::Index.new( self )
|
18
|
+
@toc = DocuBot::LinkTree::Root.new( self )
|
19
|
+
|
12
20
|
Dir.chdir( @source ) do
|
13
|
-
|
14
|
-
|
15
|
-
@
|
16
|
-
|
17
|
-
|
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( {}, index_file )
|
24
|
+
@global.glossary = @glossary
|
25
|
+
@global.index = @index
|
26
|
+
@global.toc = @toc
|
27
|
+
|
18
28
|
files_and_folders = Dir[ '**/*' ]
|
19
|
-
|
20
|
-
|
21
|
-
files_and_folders.
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
parent << page if parent
|
29
|
-
if item =~ /\b_glossary\b/
|
30
|
-
@glossary << page
|
31
|
-
end
|
32
|
-
@index.process_page( page )
|
33
|
-
|
34
|
-
# TODO: Move this bloat elsewhere.
|
35
|
-
if page.toc?
|
36
|
-
ndoc = page.nokodoc
|
37
|
-
toc = page.toc
|
38
|
-
ids = if toc[','] # Comma-delimited toc interpreted as generated ids
|
39
|
-
toc.split(/,\s*/).map{ |title| DocuBot.id_from_text(title) }
|
40
|
-
else
|
41
|
-
toc.scan /[a-z][\w.:-]*/i
|
42
|
-
end
|
43
|
-
ids.each do |id|
|
44
|
-
if ele = ndoc.at_css("##{id}")
|
45
|
-
page << DocuBot::SubLink.new( page, ele.inner_text, id )
|
46
|
-
else
|
47
|
-
warn "Could not find requested toc anchor '##{id}' on #{page.html_path}"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
else
|
53
|
-
# TODO: Anything better needed?
|
54
|
-
@extras << item
|
55
|
-
end
|
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\.[^.]+|_static|_glossary)$/ }
|
32
|
+
|
33
|
+
# All files in the _templates directory should be ignored
|
34
|
+
files_and_folders.reject!{ |f| f =~ /^_templates\b/ }
|
35
|
+
|
36
|
+
@global.ignore.as_list.each do |glob|
|
37
|
+
files_and_folders = files_and_folders - Dir[glob]
|
56
38
|
end
|
39
|
+
|
40
|
+
create_pages( files_and_folders )
|
57
41
|
end
|
42
|
+
# puts @toc.to_txt
|
58
43
|
|
59
44
|
# Regenerate pages whose templates require full scaning to have completed
|
60
45
|
# TODO: make this based off of a metasection attribute.
|
61
|
-
@
|
46
|
+
@pages.select do |page|
|
62
47
|
%w[ glossary ].include?( page.template )
|
63
48
|
end.each do |page|
|
64
49
|
page.dirty_template
|
@@ -66,20 +51,48 @@ class DocuBot::Bundle
|
|
66
51
|
|
67
52
|
# TODO: make this optional via global variable
|
68
53
|
validate_links
|
69
|
-
|
70
|
-
links.each do |link|
|
71
|
-
warn "Broken link on #{page.file}: '#{link}'"
|
72
|
-
end
|
73
|
-
end
|
54
|
+
warn_for_broken_links
|
74
55
|
|
75
56
|
# TODO: make this optional via global variable
|
76
|
-
|
77
|
-
warn "Glossary term '#{term}' never defined."
|
78
|
-
referrers.each do |referring_page|
|
79
|
-
warn "...seen on #{referring_page.file}."
|
80
|
-
end
|
81
|
-
end
|
57
|
+
warn_for_missing_glossary_terms
|
82
58
|
|
59
|
+
find_page_collisions
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_pages( files_and_folders )
|
63
|
+
files_and_folders.each do |path|
|
64
|
+
extension = File.extname( path )[ 1..-1 ]
|
65
|
+
item_is_page = File.directory?(path) || DocuBot::Converter.by_type[extension]
|
66
|
+
if !item_is_page
|
67
|
+
@extras << path
|
68
|
+
else
|
69
|
+
page = DocuBot::Page.new( self, path )
|
70
|
+
|
71
|
+
if path =~ %r{^_glossary/}
|
72
|
+
@glossary << page
|
73
|
+
else
|
74
|
+
@pages << page
|
75
|
+
@page_by_file_path[path] = page
|
76
|
+
@page_by_html_path[page.html_path] = page
|
77
|
+
@pages_by_title[page.title] << page
|
78
|
+
@index.process_page( page )
|
79
|
+
|
80
|
+
# Add the page (and any sub-links) to the toc
|
81
|
+
unless page.hide
|
82
|
+
@toc.add_to_link_hierarchy( page.title, page.html_path, page )
|
83
|
+
page.toc.as_list.each do |id_or_text|
|
84
|
+
id = id_or_text[0..0] == '#' ? id_or_text : DocuBot.id_from_text(id_or_text)
|
85
|
+
if ele = page.nokodoc.at_css(id)
|
86
|
+
@toc.add_to_link_hierarchy( ele.inner_text, page.html_path + id, page )
|
87
|
+
else
|
88
|
+
warn "Could not find requested toc anchor #{id.inspect} based on #{id_or_text.inspect} on #{page.html_path}"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
83
96
|
end
|
84
97
|
|
85
98
|
def validate_links
|
@@ -90,26 +103,26 @@ class DocuBot::Bundle
|
|
90
103
|
|
91
104
|
page_by_html_path = {}
|
92
105
|
page_by_orig_path = {}
|
93
|
-
@
|
106
|
+
@pages.each do |page|
|
94
107
|
page_by_html_path[page.html_path] = page
|
95
108
|
page_by_orig_path[page.file] = page if page.file
|
96
109
|
end
|
97
110
|
|
98
111
|
Dir.chdir( @source ) do
|
99
|
-
@
|
100
|
-
|
101
|
-
page.nokodoc.xpath('//a/@href').each do |href|
|
112
|
+
@pages.each do |page|
|
113
|
+
page.nokodoc.xpath('.//a/@href').each do |href|
|
102
114
|
href=href.content
|
103
115
|
if href=~%r{^[a-z]+://}i
|
104
116
|
@external_links[page] << href
|
105
117
|
else
|
106
|
-
id = href[
|
118
|
+
id = href[/#[a-z][\w.:-]*/i]
|
107
119
|
file = href.sub(/#.+/,'')
|
108
120
|
path = file.empty? ? page.html_path : Pathname.new( File.dirname(page.html_path) / file ).cleanpath.to_s
|
109
121
|
if target=page_by_html_path[path]
|
110
122
|
if !id || target.nokodoc.at_css(id)
|
111
123
|
@internal_links[page] << href
|
112
124
|
else
|
125
|
+
warn "Could not find internal link for #{id.inspect} on #{page.html_path.inspect}" if id
|
113
126
|
@broken_links[page] << href
|
114
127
|
end
|
115
128
|
else
|
@@ -124,6 +137,38 @@ class DocuBot::Bundle
|
|
124
137
|
end
|
125
138
|
end
|
126
139
|
end
|
140
|
+
|
141
|
+
def warn_for_broken_links
|
142
|
+
@broken_links.each do |page,links|
|
143
|
+
links.each do |link|
|
144
|
+
warn "Broken link on #{page.file}: '#{link}'"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def warn_for_missing_glossary_terms
|
150
|
+
@glossary.missing_terms.each do |term,referrers|
|
151
|
+
warn "Glossary term '#{term}' never defined."
|
152
|
+
referrers.each do |referring_page|
|
153
|
+
warn "...seen on #{referring_page.file}."
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def find_page_collisions
|
159
|
+
# Find any and all pages that would collide
|
160
|
+
pages_by_html_path = Hash.new{ |h,k| h[k] = [] }
|
161
|
+
@pages.each do |page|
|
162
|
+
pages_by_html_path[page.html_path] << page
|
163
|
+
end
|
164
|
+
collisions = pages_by_html_path.select{ |path,pages| pages.length>1 }
|
165
|
+
unless collisions.empty?
|
166
|
+
message = collisions.map do |path,pages|
|
167
|
+
"#{path}: #{pages.map{ |page| "'#{page.title}' (#{page.file})" }.join(', ')}"
|
168
|
+
end.join("\n")
|
169
|
+
raise PageCollision.new, message
|
170
|
+
end
|
171
|
+
end
|
127
172
|
|
128
173
|
def write( writer_type, destination=nil )
|
129
174
|
writer = DocuBot::Writer.by_type[ writer_type.to_s.downcase ]
|
@@ -134,4 +179,7 @@ class DocuBot::Bundle
|
|
134
179
|
end
|
135
180
|
end
|
136
181
|
|
137
|
-
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class DocuBot::Bundle::PageCollision < RuntimeError; end
|
185
|
+
|
data/lib/docubot/converter.rb
CHANGED
@@ -8,6 +8,9 @@ module DocuBot
|
|
8
8
|
def self.by_type
|
9
9
|
@by_type
|
10
10
|
end
|
11
|
+
def self.types
|
12
|
+
@by_type.keys
|
13
|
+
end
|
11
14
|
end
|
12
15
|
|
13
16
|
def self.convert_to_html( page, source, type )
|
@@ -22,4 +25,4 @@ end
|
|
22
25
|
|
23
26
|
Dir[ DocuBot::DIR/'docubot/converters/*.rb' ].each do |converter|
|
24
27
|
require converter
|
25
|
-
end
|
28
|
+
end
|
@@ -5,5 +5,5 @@ options = { :format=>:html4, :ugly=>true }
|
|
5
5
|
options.merge!( :encoding=>'utf-8' ) if Object.const_defined? "Encoding"
|
6
6
|
|
7
7
|
DocuBot::Converter.to_convert :haml do |page, source|
|
8
|
-
Haml::Engine.new( source, options ).render( page, :page=>page, :global=>page.bundle.
|
8
|
+
Haml::Engine.new( source, options ).render( page, :page=>page, :global=>page.bundle.global, :root=>page.root )
|
9
9
|
end
|
data/lib/docubot/glossary.rb
CHANGED
@@ -0,0 +1,109 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
module DocuBot::LinkTree; end
|
3
|
+
|
4
|
+
class DocuBot::LinkTree::Node
|
5
|
+
attr_accessor :title, :link, :page, :parent
|
6
|
+
|
7
|
+
def initialize( title=nil, link=nil, page=nil )
|
8
|
+
@title,@link,@page = title,link,page
|
9
|
+
@children = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def anchor
|
13
|
+
@link[/#(.+)/,1]
|
14
|
+
end
|
15
|
+
|
16
|
+
def file
|
17
|
+
@link.sub(/#.+/,'')
|
18
|
+
end
|
19
|
+
|
20
|
+
def leaf?
|
21
|
+
!@children.any?{ |node| node.page != @page }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Add a new link underneath a link to its logical parent
|
25
|
+
def add_to_link_hierarchy( title, link, page=nil )
|
26
|
+
node = DocuBot::LinkTree::Node.new( title, link, page )
|
27
|
+
parent_link = if node.anchor
|
28
|
+
node.file
|
29
|
+
elsif File.basename(link)=='index.html'
|
30
|
+
File.dirname(File.dirname(link))/'index.html'
|
31
|
+
else
|
32
|
+
(File.dirname(link) / 'index.html')
|
33
|
+
end
|
34
|
+
#puts "Adding #{title.inspect} (#{link}) to hierarchy under #{parent_link}"
|
35
|
+
parent = descendants.find{ |node| node.link==parent_link } || self
|
36
|
+
parent << node
|
37
|
+
end
|
38
|
+
|
39
|
+
def <<( node )
|
40
|
+
node.parent = self
|
41
|
+
@children << node
|
42
|
+
end
|
43
|
+
|
44
|
+
def children( parent_link=nil, &block )
|
45
|
+
if parent_link
|
46
|
+
root = find( parent_link )
|
47
|
+
root ? root.children( &block ) : []
|
48
|
+
else
|
49
|
+
@children
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def descendants
|
54
|
+
( @children + @children.map{ |child| child.descendants } ).flatten
|
55
|
+
end
|
56
|
+
|
57
|
+
def find( link )
|
58
|
+
# TODO: this is eminently cachable
|
59
|
+
descendants.find{ |node| node.link==link }
|
60
|
+
end
|
61
|
+
|
62
|
+
def depth
|
63
|
+
# Assuming no one is going to shuffle the nodes after placement
|
64
|
+
@depth ||= ancestors.length
|
65
|
+
end
|
66
|
+
|
67
|
+
def root
|
68
|
+
@root ||= "../" * (depth + ( leaf? ? 0 : 1 ))
|
69
|
+
end
|
70
|
+
|
71
|
+
def ancestors
|
72
|
+
ancestors = []
|
73
|
+
node = self
|
74
|
+
ancestors << node while node = node.parent
|
75
|
+
ancestors.reverse!
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
"#{@title} (#{@link}) - #{@page && @page.title}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_txt( depth=0 )
|
83
|
+
indent = " "*depth
|
84
|
+
[
|
85
|
+
indent+to_s,
|
86
|
+
children.map{|kid|kid.to_txt(depth+1)}
|
87
|
+
].flatten.join("\n")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class DocuBot::LinkTree::Root < DocuBot::LinkTree::Node
|
92
|
+
undef_method :title
|
93
|
+
undef_method :link
|
94
|
+
undef_method :page
|
95
|
+
attr_reader :bundle
|
96
|
+
def initialize( bundle )
|
97
|
+
@bundle = bundle
|
98
|
+
@children = []
|
99
|
+
end
|
100
|
+
|
101
|
+
def <<( node )
|
102
|
+
node.parent = nil
|
103
|
+
@children << node
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_s
|
107
|
+
"(Table of Contents)"
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
class DocuBot::MetaSection; end
|
3
|
+
module DocuBot::MetaSection::Castable
|
4
|
+
def as_boolean
|
5
|
+
self == 'true'
|
6
|
+
end
|
7
|
+
def as_list
|
8
|
+
return [] if self.nil?
|
9
|
+
# Replace commas inside quoted strings with unlikely-to-be-used Unicode
|
10
|
+
# FIXME: This doesnt work for the sadistic case of "hello """, "world"
|
11
|
+
csv = self.gsub( /("(?:[^",]|"")+),/, '\\1⥸' )
|
12
|
+
csv.split(/\s*,\s*/).map do |str|
|
13
|
+
# Put real commas back, unquote, undouble internal quotes
|
14
|
+
str[/^".*"$/] ? str[1..-2].gsub('⥸',',').gsub('""','"') : str
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class DocuBot::MetaSection
|
20
|
+
META_SEPARATOR = /^\+\+\+\s*$/ # Sort of like +++ATH0
|
21
|
+
NIL_CASTABLE = nil.extend( Castable )
|
22
|
+
attr_reader :__contents__
|
23
|
+
|
24
|
+
def initialize( attrs={}, file_path=nil )
|
25
|
+
@attrs = {}
|
26
|
+
attrs.each{ |key,value| self[key]=value }
|
27
|
+
if file_path && File.exists?( file_path )
|
28
|
+
parts = IO.read( file_path ).split( META_SEPARATOR, 2 )
|
29
|
+
if parts.length > 1
|
30
|
+
parts.first.scan(/.+/) do |line|
|
31
|
+
next if line =~ /^\s*#/
|
32
|
+
next unless line.include?(':')
|
33
|
+
attribute, value = line.split(':',2).map{ |str| str.strip }
|
34
|
+
self[attribute] = value
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@__contents__ = parts.last && parts.last.strip
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def has_key?( key )
|
42
|
+
@attrs.has_key?( key )
|
43
|
+
end
|
44
|
+
|
45
|
+
def []( attribute )
|
46
|
+
@attrs.has_key?( attribute ) ? @attrs[attribute] : NIL_CASTABLE
|
47
|
+
end
|
48
|
+
|
49
|
+
def []=( attribute, value )
|
50
|
+
@attrs[attribute.to_s] = value.extend(Castable)
|
51
|
+
end
|
52
|
+
|
53
|
+
def method_missing( method, *args )
|
54
|
+
key=method.to_s
|
55
|
+
case key[-1..-1] # the last character of the method name
|
56
|
+
when '=' then self[key[0..-2]] = args.first
|
57
|
+
else self[key]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|