rweb 0.1.0 → 0.2.0
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/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
|