rweb 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (6) hide show
  1. data/Changes.rdoc +9 -0
  2. data/README.rdoc +19 -9
  3. data/TODO.rdoc +16 -3
  4. data/bin/rweave +28 -19
  5. data/lib/rweb.rb +377 -74
  6. metadata +2 -2
@@ -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
@@ -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
- This release of _RWEB_ only handles plain text for weaving and has the basic
12
- framework for handling XHTML in the planning pipeline. It is very much a bare-
13
- bones system, albeit a usable one. The files of note are:
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 which
23
- tangle and weave respectively _RWEB_ documents and generate appropriate
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+ and, further, the style +XHTML+ is not yet implemented. Future releases
72
- will implement XHTML style and will provide tools for user-extended styles to be
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
- 1. Supply formal unit tests.
3
- 2. Provide XHTML weaving support.
4
- 3. Design generic weaving framework.
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
- This is a simple utility that acts as a wrapper around RWEB's weave and, instead
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
- The mainline code is quite simple:
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
- The only require we need to introduce is to require RWEB itself. This is not, of
47
- course, necessary if we execute the RWEB document directly. Since, however, it
48
- is highly likely that we may at some point wish to weave the RWeave utility
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
- The weaving stage itself is a very simple function which takes any kind of IO
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. This
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
- The mainline function is pretty simple as well. It just takes the arguments from
72
- the command line and passes them to the command line processor blindly,
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
- All that's left is the command line processor.
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 clutter.
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."
@@ -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 = [] ; chunks = {}
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
- # if a chunk starts, push the name of the chunk into the docs and then
111
- # extract the chunk
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.push "{<<#{name}>>}\n"
115
- get_chunk lines, name, chunks, docs
116
- # otherwise just push the line into the accumulated documentation
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
- docs.push line
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
- def get_chunk(lines, name, chunks, docs)
126
- chunks[name] = [] unless chunks.has_key?(name)
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
- # if we find the closing tag of a chunk, drop it and return
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].push line
135
- docs.push "}}} #{line}" # flag the code in the document stream
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, "invalid prefix '#{$1.strip}' when referencing chunk '#{name}'" unless $1.strip == ""
154
- raise RuntimeError, "invalid suffix '#{$3.strip}' when referencing chunk '#{name}'" unless $3.strip == ""
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}'\nexpansion chain is:\n=> #{chain.join("\n=> ")}\n" if chain.include?(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.push "#{prefix + $1}# #{name}\n"
159
- out.push "#{prefix + $1}# #{"-" * name.length}\n"
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.push prefix + line
208
+ out << prefix + line
164
209
  end
165
210
  end
166
211
  out
167
212
  end
168
213
 
169
- # Generate plaintext documentation from the documentation intermediate form.
170
- # The following actions are taken:
171
- # 1. The title is placed at the top and double-underlined.
172
- # 2. Documentation lines are passed through unaltered.
173
- # 3. Chunk names are emitted without the tags and with single-underlining.
174
- # 4. Chunk contents are emitted with "> " prepended.
175
- def generate_plain_document(docs, directives)
176
- raise RuntimeError, "style directive not set to Plain" unless directives[:style] == "Plain"
177
- out = []
178
- title = directives[:title]
179
-
180
- out << ["#{title}\n","#{"="*title.length}\n"]
181
- docs.each do |line|
182
- # if we find a chunk reference, extract the name and underline it, the
183
- # mainline chunk being given the title "RWEB Mainline"
184
- if %r{^\{\<\<([[:print:]]*)\>\>\}\n$}.match line
185
- line = $1
186
- line = "RWEB Mainline" if line == ""
187
- out << ["#{line}\n","#{"-"*line.length}\n"]
188
- # if we find a line of code, emit it with added "chicken feet" to the front.
189
- elsif %r{^\}\}\} (.*)\n$}.match line
190
- out << ["> #{$1}\n"]
191
- # otherwise just emit the line
192
- else
193
- out << line
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
- # Generate XHTML documentation from the documentation intermediate form.
200
- # NOT YET IMPLEMENTED!
201
- def generate_xhtml_document(docs, directives)
202
- raise RuntimeError, "style directive not set to XHTML" unless directives[:style] == "XHTML"
203
- raise NotImplementedError, "document style not implemented"
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 only
208
- three publicly-accessible symbols. First, the version of the library is
209
- provided (for build purposes). Second, the RWEB.tangle function is exposed
210
- which takes an IO object and returns a string containing the tangled code ready
211
- for execution. Third, the RWEB.weave function is exposed which takes an IO
212
- object and returns a string containing the documentation in the appropriate
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
- VERSION = "0.1.0"
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
- # Tangle an RWEB document and return the code as a string.
220
- def RWEB.tangle(io)
221
- directives, docs, chunks = disassemble(io.readlines)
222
- raise RuntimeError, "no mainline chunk found in file" unless chunks.has_key?("")
223
- "# #{directives[:title]}\n# #{"=" * directives[:title].length}\n\n" + expand_chunk(chunks[""], chunks).to_s
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
- # Weave the RWEB document into another format according to directives and
227
- # return as a string.
228
- def RWEB.weave(io)
229
- directives, docs, chunks = disassemble(io.readlines)
230
- eval "generate_#{directives[:style].downcase}_document(docs, directives)"
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.1.0
7
- date: 2007-08-04 00:00:00 +08:00
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