docubot 0.3.3 → 0.4
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.
- 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
|