rweb 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Changes.rdoc +9 -0
- data/README.rdoc +19 -9
- data/TODO.rdoc +16 -3
- data/bin/rweave +28 -19
- data/lib/rweb.rb +377 -74
- metadata +2 -2
data/Changes.rdoc
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
= Release History
|
2
|
+
Version 0.2.0 (2007-08-18)::
|
3
|
+
- complete library for tangling and weaving both plaintext and XHTML
|
4
|
+
- XHTML weaving will make use of the +syntax+ gem if available but does not
|
5
|
+
require it
|
6
|
+
- RDOC documentation updated and expanded
|
7
|
+
- test suite expanded and hardened
|
8
|
+
- internal representation of parsed RWEB document completely rewritten
|
9
|
+
- plug-in architecture provided in bare-bones format (no external interace yet)
|
10
|
+
|
2
11
|
Version 0.1.0 (2007-08-04)::
|
3
12
|
- complete library for tangling as well as for weaving to plaintext
|
4
13
|
- RDOC documentation completed
|
data/README.rdoc
CHANGED
@@ -6,11 +6,16 @@ Welcome to the _RWEB_ package. _RWEB_ is several things:
|
|
6
6
|
Ruby programs in the more traditional form you see based on Knuth's WEB;
|
7
7
|
- it is a framework for customizing the weaving process to enable any back-end
|
8
8
|
the user desires to use for generating documentation.
|
9
|
+
|
10
|
+
The _RWEB_ package can be retrieved from its {Rubyforge project
|
11
|
+
page}[http://rubyforge.org/projects/rubylit/].
|
9
12
|
|
10
13
|
== This release
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
|
15
|
+
This release of _RWEB_ handles both plain text and XHTML for weaving and has the
|
16
|
+
beginnings of a framework for user-supplied plug-ins in the planning pipeline.
|
17
|
+
It is very much a usable system, however, with the XHTML output in particular
|
18
|
+
being well-suited to blogging. The files of note are:
|
14
19
|
|
15
20
|
lib/rweb.rb::
|
16
21
|
This is the library file that does the heavy lifting for the package. It needs
|
@@ -19,9 +24,9 @@ lib/rweb.rb::
|
|
19
24
|
module is private utility functions.
|
20
25
|
|
21
26
|
bin/rtangle and bin/rweave::
|
22
|
-
These scripts are self-tangling _RWEB_ documents in plain text format
|
23
|
-
|
24
|
-
output.
|
27
|
+
These scripts are self-tangling _RWEB_ documents in plain text format
|
28
|
+
(rtangle) and XHTML format (rweave) which tangle and weave respectively _RWEB_
|
29
|
+
documents and generate appropriate output.
|
25
30
|
|
26
31
|
COPYING::
|
27
32
|
The license under which this program is released. Read it. Know your rights
|
@@ -68,9 +73,14 @@ Currently only two keys are supported:
|
|
68
73
|
|
69
74
|
The +style+ key reflects the documentation style used in the underlying _RWEB_
|
70
75
|
document. As of this version the style can only accept the values +Plain+ and
|
71
|
-
+XHTML
|
72
|
-
|
73
|
-
included in the mix.
|
76
|
+
+XHTML+. Future releases will implement other styles (most notably
|
77
|
+
Bluecloth/Markdown) and will provide tools for user-extended styles to be
|
78
|
+
included in the mix. (The beginnings of this framework is already in place.)
|
79
|
+
|
80
|
+
XHTML processing provides optional syntax highlighting automatically. If the
|
81
|
+
{syntax library}[http://rubyforge.org/projects/syntax/] is available the weaving
|
82
|
+
functionality uses it seamlessly behind the scenes. If it is not available for
|
83
|
+
use, the Ruby code blocks are passed through unaltered.
|
74
84
|
|
75
85
|
The title key is simply text used while generating the documentation and code
|
76
86
|
which provides a title to the work. If left unset, it defaults to +Untitled+.
|
data/TODO.rdoc
CHANGED
@@ -1,4 +1,17 @@
|
|
1
1
|
= TODO
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
==Supply more formal unit tests.
|
3
|
+
|
4
|
+
==Design generic weaving plug-in framework.
|
5
|
+
This is partially started in that both plaintext and XHTML are supported through
|
6
|
+
externally plugged-in classes. There are no mechanisms yet, however, for
|
7
|
+
registering user classes so this still requires some more thinking.
|
8
|
+
|
9
|
+
==Rethink the "plain text" format.
|
10
|
+
It may be desirable, in fact, to have Markdown be the default "plain text"
|
11
|
+
format. When weaving it can be left up to directives (or command line options,
|
12
|
+
for that matter) to decide whether we really want "plain text" or Markdown's
|
13
|
+
HTML output. Since both formats have their uses -- Markdown is easily-readable
|
14
|
+
as-is while HTML output is useful for things like blogging, etc. -- we could
|
15
|
+
probably get away with using Markdown as the default format. Further we could
|
16
|
+
even make the choice of output automatic: if the Bluecloth library is available,
|
17
|
+
we process it. If it isn't, we don't.
|
data/bin/rweave
CHANGED
@@ -24,14 +24,18 @@ eval RWEB.tangle(DATA)
|
|
24
24
|
|
25
25
|
__END__
|
26
26
|
{title => RWEAVE}
|
27
|
+
{style => XHTML}
|
27
28
|
|
28
|
-
|
29
|
-
of executing the resulting code, either prints it to stdout or saves it to a
|
30
|
-
given file. Note that RWeave is itself written in RWEB's executable format with
|
31
|
-
the boilerplate before the __END__ statement and the actual program document
|
32
|
-
afterwards.
|
29
|
+
<h2>Introduction</h2>
|
33
30
|
|
34
|
-
|
31
|
+
<p>This is a simple utility that acts as a wrapper around RWEB's weave and,
|
32
|
+
instead of executing the resulting code, either prints it to stdout or saves it
|
33
|
+
to a given file. Note that RWeave is itself written in RWEB's executable format
|
34
|
+
with the boilerplate before the __END__ statement and the actual program
|
35
|
+
document afterwards.</p>
|
36
|
+
|
37
|
+
<h3>Mainline</h3>
|
38
|
+
<p>The mainline code is quite simple:</p>
|
35
39
|
|
36
40
|
<< {
|
37
41
|
{<<global requires>>}
|
@@ -43,21 +47,23 @@ The mainline code is quite simple:
|
|
43
47
|
{<<main>>}
|
44
48
|
}>>
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
|
50
|
+
<h3>Requires</h3>
|
51
|
+
<p>The only require we need to introduce is to require RWEB itself. This is not,
|
52
|
+
of course, necessary if we execute the RWEB document directly. Since, however,
|
53
|
+
it is highly likely that we may at some point wish to weave the RWeave utility
|
49
54
|
itself into a Ruby source file, we should, pro forma, require the library
|
50
|
-
anyway
|
55
|
+
anyway.</p>
|
51
56
|
|
52
57
|
<< global requires {
|
53
58
|
require 'rweb'
|
54
59
|
}>>
|
55
60
|
|
56
|
-
|
61
|
+
<h3>Weaving</h3>
|
62
|
+
<p>The weaving stage itself is a very simple function which takes any kind of IO
|
57
63
|
object for input, weaves it, then places the resulting string into the output,
|
58
64
|
itself an IO object of any kind. It also doesn't care in the slightest exactly
|
59
65
|
what kind of IO object is being used, thus suited to application as a filter in
|
60
|
-
a potentially longer chain of tools
|
66
|
+
a potentially longer chain of tools.</p>
|
61
67
|
|
62
68
|
<< weaving {
|
63
69
|
def rweave(io_in, io_out)
|
@@ -65,17 +71,18 @@ def rweave(io_in, io_out)
|
|
65
71
|
end
|
66
72
|
}>>
|
67
73
|
|
68
|
-
As can be seen, the true heavy lifting is done inside of the RWEB module.
|
69
|
-
utility is a hyper-simple shell around it by comparison
|
74
|
+
<p>As can be seen, the true heavy lifting is done inside of the RWEB module.
|
75
|
+
This utility is a hyper-simple shell around it by comparison.</p>
|
70
76
|
|
71
|
-
|
72
|
-
|
77
|
+
<h3>Mainline code</h3>
|
78
|
+
<p>The mainline function is pretty simple as well. It just takes the arguments
|
79
|
+
from the command line and passes them to the command line processor blindly,
|
73
80
|
accepting in return a pair of IO objects -- one for input, the other for output.
|
74
81
|
It then calls weave with these objects, wrapping in a begin/ensure clause to
|
75
82
|
make sure the IO objects are closed before exiting. The only additional step it
|
76
83
|
takes is a call to the function find_block_begin which shadows the IO object
|
77
84
|
used for input and silently sweeps away the stuff before __END__ in a document
|
78
|
-
if it exists in the first 20 lines
|
85
|
+
if it exists in the first 20 lines.</p>
|
79
86
|
|
80
87
|
<< main {
|
81
88
|
io_in, io_out = process_argv ARGV
|
@@ -87,7 +94,8 @@ ensure
|
|
87
94
|
end
|
88
95
|
}>>
|
89
96
|
|
90
|
-
|
97
|
+
<h3>Command line processing</h3>
|
98
|
+
<p>All that's left is the command line processor.</p>
|
91
99
|
|
92
100
|
<< command line processing {
|
93
101
|
def process_argv args
|
@@ -104,7 +112,8 @@ def process_argv args
|
|
104
112
|
end
|
105
113
|
}>>
|
106
114
|
|
107
|
-
The usage message is straightforward and broken out only to reduce code
|
115
|
+
<p>The usage message is straightforward and broken out only to reduce code
|
116
|
+
clutter.</p>
|
108
117
|
|
109
118
|
<< display usage {
|
110
119
|
puts "Incorrect command line."
|
data/lib/rweb.rb
CHANGED
@@ -30,7 +30,18 @@ class Object
|
|
30
30
|
def deep_copy
|
31
31
|
Marshal.load(Marshal.dump(self))
|
32
32
|
end
|
33
|
-
|
33
|
+
end
|
34
|
+
|
35
|
+
=begin rdoc
|
36
|
+
Code chunks need to be expanded a bit to associate a name with them. This serves
|
37
|
+
partially to assist in creating titles and partially to find the code chunks
|
38
|
+
when expanding them. Documentation chunks need no enhancement.
|
39
|
+
=end
|
40
|
+
class CodeChunk < Array
|
41
|
+
def initialize(name = "")
|
42
|
+
@name = name
|
43
|
+
end
|
44
|
+
attr_reader :name
|
34
45
|
end
|
35
46
|
|
36
47
|
# The default values for directives. The default style for weaving is plain
|
@@ -52,11 +63,13 @@ end
|
|
52
63
|
# Strip the boilerplate from self-tangling scripts before processing the rest of
|
53
64
|
# the document.
|
54
65
|
def strip_boilerplate(lines)
|
66
|
+
|
55
67
|
# if we find the __END__ directive in the lines, we return all the lines after
|
56
68
|
# that point
|
57
69
|
h = 0
|
58
70
|
if lines.find { |l| h += 1 ; l == "__END__\n" }
|
59
71
|
return lines[h..-1]
|
72
|
+
|
60
73
|
# otherwise this is not a self-tangling script so all the lines count
|
61
74
|
else
|
62
75
|
return lines
|
@@ -69,13 +82,16 @@ end
|
|
69
82
|
# beginning of the documentation block and reinserted into the stream.
|
70
83
|
def find_directives(lines, directives)
|
71
84
|
while line = lines.shift
|
85
|
+
|
72
86
|
# skip blank lines
|
73
87
|
if %r{^[:blank:]*$}.match line
|
74
88
|
next
|
89
|
+
|
75
90
|
# process directive lines
|
76
91
|
elsif %r{^\{([[:print:]]+)[[:blank:]]+=>[[:blank:]]+([[:print:]]+)\}\n$}.match line
|
77
92
|
set_directive $1, $2, directives if directives
|
78
93
|
next
|
94
|
+
|
79
95
|
# restore the first line of documentation, return the directives
|
80
96
|
else
|
81
97
|
return directives, lines.unshift(line)
|
@@ -84,8 +100,10 @@ def find_directives(lines, directives)
|
|
84
100
|
end
|
85
101
|
|
86
102
|
# Take a given directive key/value pair and set if if valid. Valid keys are, at
|
87
|
-
# this point, "style" and "title". "style" may have the values "Plain" or
|
88
|
-
# "XHTML" (not currently implemented).
|
103
|
+
# this point, "style" and "title". "style" may have the values "Plain" or
|
104
|
+
# "XHTML" (not currently implemented). This function needs to be replaced with
|
105
|
+
# a more powerful registration scheme when the user-supplied plugins are
|
106
|
+
# released.
|
89
107
|
def set_directive(key, value, directives)
|
90
108
|
case key
|
91
109
|
when "style"
|
@@ -102,132 +120,417 @@ def set_directive(key, value, directives)
|
|
102
120
|
end
|
103
121
|
|
104
122
|
# Break the main body of an RWEB document into the documentation intermediate
|
105
|
-
# form and a collection of unexpanded chunks.
|
123
|
+
# form and a collection of assembled, but unexpanded chunks.
|
106
124
|
def get_docs_and_chunks(lines)
|
107
|
-
docs = []
|
125
|
+
docs = []
|
126
|
+
chunks = Hash.new {|h,k| h[k]=CodeChunk.new(k)}
|
127
|
+
current_doc = []
|
128
|
+
|
108
129
|
# directives are already gone, so we start in documentation
|
109
130
|
while line = lines.shift
|
110
|
-
|
111
|
-
#
|
131
|
+
|
132
|
+
# if a code chunk starts, close off the current documentation chunk, append
|
133
|
+
# it to the docs array, extract the code chunk, append that to the docs
|
134
|
+
# array and start a new documentation chunk
|
112
135
|
if %r{^\<\<([[:print:]]*)\{[[:blank:]]*$}.match line
|
136
|
+
docs << current_doc
|
113
137
|
name = $1.strip
|
114
|
-
docs
|
115
|
-
|
116
|
-
|
138
|
+
docs << get_chunk(lines, name, chunks)
|
139
|
+
current_doc = []
|
140
|
+
|
141
|
+
# otherwise just push the line into the current documentation chunk
|
117
142
|
else
|
118
|
-
|
143
|
+
current_doc << line
|
119
144
|
end
|
120
145
|
end
|
146
|
+
docs << current_doc
|
121
147
|
return docs, chunks
|
122
148
|
end
|
123
149
|
|
124
150
|
# Extract a detected chunk of code.
|
125
|
-
|
126
|
-
|
151
|
+
# This function does these things:
|
152
|
+
# 1. It extracts a chunk fragment from the RWEB document.
|
153
|
+
# 2. It appends the chunk fragment to any code from previous chunk fragments
|
154
|
+
# with the same name.
|
155
|
+
# 3. It returns the chunk fragment for inclusion into the RWEB document stream.
|
156
|
+
def get_chunk(lines, name, chunks)
|
157
|
+
current_chunk = CodeChunk.new(name) # this is for the docs array
|
158
|
+
|
127
159
|
while line = lines.shift
|
128
|
-
|
160
|
+
|
161
|
+
# if we find the closing tag of a chunk, drop it and return total current
|
162
|
+
# chunk
|
129
163
|
if %r{^\}\>\>[[:blank:]]*$}.match line
|
130
|
-
return
|
164
|
+
return current_chunk # return the partial chunk for the docs array
|
165
|
+
|
131
166
|
# otherwise add the current line to the named chunk and add the code,
|
132
167
|
# marked, to the documentation array for weaving
|
133
168
|
else
|
134
|
-
chunks[name]
|
135
|
-
|
169
|
+
chunks[name] << line # accumulated chunk for tangling
|
170
|
+
current_chunk << line # partial chunk for documentation only
|
136
171
|
end
|
137
172
|
end
|
138
173
|
raise RuntimeError, "chunk '#{name}' not closed by end of input"
|
139
174
|
end
|
140
175
|
|
141
|
-
# Take a raw chunk and expand all internal references.
|
176
|
+
# Take a raw chunk and expand all internal references; used only for tangling.
|
142
177
|
def expand_chunk(chunk, chunks, prefix = "", chain = [])
|
143
178
|
out = []
|
144
179
|
while line = chunk.shift
|
180
|
+
|
145
181
|
# if we spot a chunk reference, validate it and expand its contents into the
|
146
182
|
# current chunk
|
147
183
|
if %r{^([[:print:][:blank:]]*)\{\<\<([[:print:]]+)\>\>\}([[:print:][:blank:]]*)$}.match line
|
148
184
|
name = $2.strip
|
185
|
+
|
149
186
|
# Illegal conditions are:
|
150
187
|
# 1. A chunk referenced which has not been defined.
|
151
188
|
raise RuntimeError, "chunk '#{name}' referenced without definition" unless chunks.has_key?(name)
|
189
|
+
|
152
190
|
# 2. A chunk reference with anything but whitespace in front or behind.
|
153
|
-
raise RuntimeError, "
|
154
|
-
|
191
|
+
raise RuntimeError, "bad prefix '#{$1.strip}' using chunk '#{name}'"\
|
192
|
+
unless $1.strip == ""
|
193
|
+
raise RuntimeError, "bad suffix '#{$3.strip}' using chunk '#{name}'"\
|
194
|
+
unless $3.strip == ""
|
195
|
+
|
155
196
|
# 3. A circular expansion of a chunk.
|
156
|
-
raise RuntimeError, "circular expansion of chunk '#{name}'\
|
197
|
+
raise RuntimeError, "circular expansion of chunk '#{name}'\n"\
|
198
|
+
"expansion chain is:\n=> #{chain.join("\n=> ")}\n"\
|
199
|
+
if chain.include?(name)
|
200
|
+
|
201
|
+
# Anything else is a legal condition.
|
157
202
|
chain.push name
|
158
|
-
out
|
159
|
-
out
|
203
|
+
out << "#{prefix + $1}# #{name}\n"
|
204
|
+
out << "#{prefix + $1}# #{"-" * name.length}\n"
|
160
205
|
out += expand_chunk(chunks[name], chunks, prefix + $1, chain)
|
161
206
|
chain.pop
|
162
207
|
else
|
163
|
-
out
|
208
|
+
out << prefix + line
|
164
209
|
end
|
165
210
|
end
|
166
211
|
out
|
167
212
|
end
|
168
213
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
214
|
+
=begin rdoc
|
215
|
+
This class uses a dynamically-assigned generator class to walk the documentation
|
216
|
+
array and build the final output document.
|
217
|
+
=end
|
218
|
+
class Document
|
219
|
+
|
220
|
+
public
|
221
|
+
|
222
|
+
# Initializes the document system with an appropriate generator, checks for
|
223
|
+
# sanity in the setup and then builds the document internally.
|
224
|
+
def initialize(generator)
|
225
|
+
@output = []
|
226
|
+
@generator=generator
|
227
|
+
@style = generator.style
|
228
|
+
@docs = generator.docs
|
229
|
+
@directives = generator.directives
|
230
|
+
check_sanity
|
231
|
+
build_document
|
232
|
+
end
|
233
|
+
|
234
|
+
# Converts the built document into a string.
|
235
|
+
def to_s
|
236
|
+
@output.to_s
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
# The workhorse of the weaving system. Builds up output form by:
|
242
|
+
# 1. Using the generator to build a header based on the title of the RWEB
|
243
|
+
# document.
|
244
|
+
# 2. Walking over the documentation array and passing each code or document
|
245
|
+
# chunk as appropriate to the generator.
|
246
|
+
# 3. Using the generator to append a footer to the output.
|
247
|
+
def build_document
|
248
|
+
@output << @generator.header(@directives[:title])
|
249
|
+
@docs.each do |chunk|
|
250
|
+
if chunk.instance_of? CodeChunk
|
251
|
+
@output << @generator.code(chunk)
|
252
|
+
else
|
253
|
+
@output << @generator.doc(chunk)
|
254
|
+
end
|
194
255
|
end
|
256
|
+
@output << @generator.footer
|
195
257
|
end
|
196
|
-
out.to_s
|
197
|
-
end
|
198
258
|
|
199
|
-
#
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
259
|
+
# Check to ensure the sanity of the system before trying to build documents.
|
260
|
+
def check_sanity
|
261
|
+
|
262
|
+
# make sure the actual document style matches the generator's expected style
|
263
|
+
raise RuntimeError, "style mismatch: #{@style} to be processed, " \
|
264
|
+
"#{@directives[:style]} passed" \
|
265
|
+
unless @style == @directives[:style]
|
266
|
+
end
|
204
267
|
end
|
205
268
|
|
206
269
|
=begin rdoc
|
207
|
-
The RWEB module is the public interface to the RWEB library and contains
|
208
|
-
|
209
|
-
|
210
|
-
which takes an IO object and returns a string
|
211
|
-
|
212
|
-
|
213
|
-
format.
|
270
|
+
The RWEB module is the public interface to the RWEB library and contains the
|
271
|
+
following publicly-accessible symbols:
|
272
|
+
1. The version of the library (VERSION).
|
273
|
+
2. The RWEB.tangle function which takes an IO object and returns a string
|
274
|
+
containing the tangled code ready for execution.
|
275
|
+
3. The RWEB.weave function which takes an IO object and returns a string
|
276
|
+
containing the documentation in the appropriate format.
|
277
|
+
4. The Generator class, an "abstract class" which forces any document generation
|
278
|
+
to conform to the correct footprint. Any generator for weaving back-ends
|
279
|
+
must inherit from this generic class and must call super in its
|
280
|
+
initialization.
|
214
281
|
=end
|
215
282
|
module RWEB
|
216
283
|
|
217
|
-
|
284
|
+
VERSION = "0.2.0"
|
285
|
+
|
286
|
+
# Tangle an RWEB document and return the code as a string.
|
287
|
+
def RWEB.tangle(io)
|
288
|
+
directives, docs, chunks = disassemble(io.readlines)
|
289
|
+
raise RuntimeError, "no mainline chunk found in file"\
|
290
|
+
unless chunks.has_key?("")
|
291
|
+
|
292
|
+
"# #{directives[:title]}\n"\
|
293
|
+
"# #{"=" * directives[:title].length}\n\n"\
|
294
|
+
+ expand_chunk(chunks[""], chunks).to_s
|
295
|
+
end
|
296
|
+
|
297
|
+
# Weave the RWEB document into another format according to directives and
|
298
|
+
# return as a string.
|
299
|
+
def RWEB.weave(io)
|
300
|
+
directives, docs, chunks = disassemble(io.readlines)
|
301
|
+
eval "Document.new(#{directives[:style]}Generator.new(docs, directives)).to_s"
|
302
|
+
end
|
303
|
+
|
304
|
+
=begin rdoc
|
305
|
+
The core of document generation is this Generator class, an abstract class that
|
306
|
+
sets the footprint and makes sure that any implementing class has all required
|
307
|
+
methods covered. Generators must provide all of the methods with proper
|
308
|
+
semantics outlined in each method's documentation.
|
309
|
+
|
310
|
+
Any state changes in the documentation (like the transition from code block to
|
311
|
+
document block) must be handled by identifying the state changes through
|
312
|
+
methods.
|
313
|
+
=end
|
314
|
+
class Generator
|
315
|
+
|
316
|
+
# This method gets the array of documentation/code chunks and the block of
|
317
|
+
# detected directives. Any derived class *must* ensure that docs and
|
318
|
+
# directives are appropriately passed up the chain to this implementation.
|
319
|
+
# Too, any derived class *must* ensure that an @style member is set to the
|
320
|
+
# appropriate documentation style (as per the "style" keyword in directives).
|
321
|
+
def initialize(docs, directives)
|
322
|
+
@docs = docs
|
323
|
+
@directives = directives
|
324
|
+
end
|
325
|
+
attr_reader :style, :docs, :directives
|
218
326
|
|
219
|
-
#
|
220
|
-
|
221
|
-
|
222
|
-
raise
|
223
|
-
|
327
|
+
# This method gets called at the beginning of the document for initialization
|
328
|
+
# of the document format: e.g. XHTML's <html>, <head> and <body> tags.
|
329
|
+
def header(title)
|
330
|
+
raise NotImplementedError, "#{self.class.name}#header is an abstract method."
|
331
|
+
end
|
332
|
+
|
333
|
+
# This method gets called to perform any transformations on documentation
|
334
|
+
# chunks. For example Markdown-formatted plaintext might get run through
|
335
|
+
# BlueCloth to get turned into proper HTML here.
|
336
|
+
def doc(chunk)
|
337
|
+
raise NotImplementedError, "#{self.class.name}#doc is an abstract method."
|
338
|
+
end
|
339
|
+
|
340
|
+
# This method gets called to perform any transformations on code chunks. For
|
341
|
+
# example code can be syntax-highlighted.
|
342
|
+
def code(chunk)
|
343
|
+
raise NotImplementedError, "#{self.class.name}#code is an abstract method."
|
224
344
|
end
|
225
345
|
|
226
|
-
#
|
227
|
-
#
|
228
|
-
def
|
229
|
-
|
230
|
-
|
346
|
+
# This method gets called at the end of the document for closing document
|
347
|
+
# sequences: e.g. XHTML's </html> and </body> tags.
|
348
|
+
def footer
|
349
|
+
raise NotImplementedError, "#{self.class.name}#footer is an abstract method."
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
end # module RWEB
|
354
|
+
|
355
|
+
=begin rdoc
|
356
|
+
PlainGenerator is a simple object that does very little processing on code
|
357
|
+
and/or document lines. Its implementation is similarly simple.
|
358
|
+
=end
|
359
|
+
class PlainGenerator < RWEB::Generator
|
360
|
+
|
361
|
+
# The initialize function just sets the style and does no other preparation.
|
362
|
+
def initialize(*args)
|
363
|
+
@style = "Plain"
|
364
|
+
super
|
231
365
|
end
|
232
366
|
|
367
|
+
# The header function prints out the title and then "double-underlines" it.
|
368
|
+
def header(title)
|
369
|
+
"#{title}\n" "#{"="*title.length}\n"
|
370
|
+
end
|
371
|
+
|
372
|
+
# The chunk function prints out the name and "single-underlines" it, then
|
373
|
+
# follows through with printing each code line with pre-pended "chicken feet".
|
374
|
+
def code(chunk)
|
375
|
+
"#{chunk.name}\n" "#{"-"*chunk.name.length}\n" +
|
376
|
+
chunk.map { |line| "> " + line}.to_s
|
377
|
+
end
|
378
|
+
|
379
|
+
# The doc function passes the documentation chunk through unchanged.
|
380
|
+
def doc(chunk)
|
381
|
+
chunk.to_s
|
382
|
+
end
|
383
|
+
|
384
|
+
# The footer function does nothing.
|
385
|
+
def footer
|
386
|
+
""
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
=begin rdoc
|
391
|
+
XHTMLGenerator is a more-involved object that does some simple, but verbose and
|
392
|
+
tedious, alteration of the incoming document.
|
393
|
+
=end
|
394
|
+
class XHTMLGenerator < RWEB::Generator
|
395
|
+
|
396
|
+
class DoNothingConverter
|
397
|
+
def convert(code)
|
398
|
+
code
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
# A more involved initialization block. It tries to bring in the syntax gem
|
403
|
+
# and, if successful, sets up syntax conversion. If unsuccessful it puts in
|
404
|
+
# a dummy function for "converting" which does nothing.
|
405
|
+
def initialize(*args)
|
406
|
+
# First we check for the presence of RubyGems.
|
407
|
+
begin
|
408
|
+
begin
|
409
|
+
require 'rubygems'
|
410
|
+
rescue LoadError
|
411
|
+
# we don't have rubygems, but that's OK -- syntax may be installed anyway
|
412
|
+
end
|
413
|
+
require 'syntax/convertors/html'
|
414
|
+
@html_converter = Syntax::Convertors::HTML.for_syntax "ruby"
|
415
|
+
rescue LoadError
|
416
|
+
@html_converter = DoNothingConverter.new
|
417
|
+
end
|
418
|
+
|
419
|
+
@style = "XHTML"
|
420
|
+
super
|
421
|
+
end
|
422
|
+
|
423
|
+
# This is tedious, but not complicated. It writes the boilerplate that is the
|
424
|
+
# curse of HTML documentation and any CSS that has been set up out-of-band.
|
425
|
+
# (Of course the documentation portion of the RWEB document can just as easily
|
426
|
+
# contain CSS as it can anything else.) The syntax-highlighting CSS is
|
427
|
+
# taken from the syntax gem verbatim as a default.
|
428
|
+
def header(title)
|
429
|
+
<<-HEADER
|
430
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
431
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
432
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
433
|
+
<head>
|
434
|
+
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
|
435
|
+
<title>#{title}</title>
|
436
|
+
<style type="text/css">
|
437
|
+
.chunk-title {
|
438
|
+
border-bottom: thin solid;
|
439
|
+
font-style: italic;
|
440
|
+
font-weight: bold;
|
441
|
+
width: 80%;
|
442
|
+
}
|
443
|
+
.chunk-contents {
|
444
|
+
border-bottom: thin dotted;
|
445
|
+
font-family: monospace;
|
446
|
+
overflow: auto;
|
447
|
+
width: 80%;
|
448
|
+
}
|
449
|
+
a.chunk-reference {
|
450
|
+
border: thin dotted red;
|
451
|
+
font-style: italic;
|
452
|
+
padding: 1px;
|
453
|
+
}
|
454
|
+
.normal {}
|
455
|
+
.comment { color: #005; font-style: italic; }
|
456
|
+
.keyword { color: #A00; font-weight: bold; }
|
457
|
+
.method { color: #077; }
|
458
|
+
.class { color: #074; }
|
459
|
+
.module { color: #050; }
|
460
|
+
.punct { color: #447; font-weight: bold; }
|
461
|
+
.symbol { color: #099; }
|
462
|
+
.string { color: #944; background: #FFE; }
|
463
|
+
.char { color: #F07; }
|
464
|
+
.ident { color: #004; }
|
465
|
+
.constant { color: #07F; }
|
466
|
+
.regex { color: #B66; background: #FEF; }
|
467
|
+
.number { color: #F99; }
|
468
|
+
.attribute { color: #7BB; }
|
469
|
+
.global { color: #7FB; }
|
470
|
+
.expr { color: #227; }
|
471
|
+
.escape { color: #277; }
|
472
|
+
</style>
|
473
|
+
</head>
|
474
|
+
<body>
|
475
|
+
<h1>#{title}</h1>
|
476
|
+
HEADER
|
477
|
+
end
|
478
|
+
|
479
|
+
# A chunk is now written with the title in its own special division and given
|
480
|
+
# a hypertext name. A code block, too, is opened with appropriate tags.
|
481
|
+
def code(chunk)
|
482
|
+
header = <<-CHUNK_HEADER
|
483
|
+
<blockquote>
|
484
|
+
<div class="chunk-title">
|
485
|
+
<a name="#{chunk.name}" id="#{chunk.name}">#{chunk.name}</a>
|
486
|
+
</div>
|
487
|
+
<div class="chunk-contents"><pre><code>
|
488
|
+
CHUNK_HEADER
|
489
|
+
header = header[0..-2]
|
490
|
+
|
491
|
+
body = ""
|
492
|
+
subchunk = ""
|
493
|
+
chunk.each do |line|
|
494
|
+
# if we have a chunk reference, syntax-highlight the subchunk we're
|
495
|
+
# building, add it to the body, make an href of the chunk reference, add
|
496
|
+
# it to the body, then restart the subchunking.
|
497
|
+
if %r{^([[:print:][:blank:]]*)\{\<\<([[:print:]]+)\>\>\}([[:print:][:blank:]]*)$}.match line
|
498
|
+
prefix = $1
|
499
|
+
name = $2.strip
|
500
|
+
suffix = $3
|
501
|
+
|
502
|
+
body += @html_converter.convert(subchunk)
|
503
|
+
body += prefix + "<a class=\"chunk-reference\" href=\"##{name}\">" + name + "</a>" + suffix + "\n"
|
504
|
+
subchunk = ""
|
505
|
+
|
506
|
+
# add the line to the subchunk
|
507
|
+
else
|
508
|
+
subchunk += line
|
509
|
+
end
|
510
|
+
end
|
511
|
+
body += @html_converter.convert(subchunk)[0..-2]
|
512
|
+
body.strip!
|
513
|
+
|
514
|
+
footer = <<-CHUNK_FOOTER
|
515
|
+
</code></pre></div>
|
516
|
+
</blockquote>
|
517
|
+
CHUNK_FOOTER
|
518
|
+
|
519
|
+
header + body + footer
|
520
|
+
end
|
521
|
+
|
522
|
+
# The documentation requires no processing -- it's XHTML and already
|
523
|
+
# "processed". Copy it through.
|
524
|
+
def doc(chunk)
|
525
|
+
chunk.to_s
|
526
|
+
end
|
527
|
+
|
528
|
+
# This closes off all tags left open from code blocks or paragraphs as well as
|
529
|
+
# from the header.
|
530
|
+
def footer
|
531
|
+
<<-FOOTER
|
532
|
+
</body>
|
533
|
+
</html>
|
534
|
+
FOOTER
|
535
|
+
end
|
233
536
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
|
|
3
3
|
specification_version: 1
|
4
4
|
name: rweb
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.
|
7
|
-
date: 2007-08-
|
6
|
+
version: 0.2.0
|
7
|
+
date: 2007-08-18 00:00:00 +08:00
|
8
8
|
summary: Self-executing WEB-like literate programming for Ruby.
|
9
9
|
require_paths:
|
10
10
|
- lib
|