sqlpostgres 1.2.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.
Files changed (207) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +22 -0
  3. data/LICENSE.md +23 -0
  4. data/README.rdoc +59 -0
  5. data/Rakefile +32 -0
  6. data/VERSION +1 -0
  7. data/doc/BUGS +2 -0
  8. data/doc/examples/README +6 -0
  9. data/doc/examples/connection.rb +16 -0
  10. data/doc/examples/connection_auto.rb +22 -0
  11. data/doc/examples/connection_ctor.rb +18 -0
  12. data/doc/examples/connection_default.rb +15 -0
  13. data/doc/examples/connection_exec.rb +18 -0
  14. data/doc/examples/connection_manual.rb +12 -0
  15. data/doc/examples/connection_wrapped_new.rb +13 -0
  16. data/doc/examples/connection_wrapped_open.rb +13 -0
  17. data/doc/examples/cursor.rb +38 -0
  18. data/doc/examples/include_module.rb +9 -0
  19. data/doc/examples/include_module2.rb +12 -0
  20. data/doc/examples/insert.rb +30 -0
  21. data/doc/examples/insert2.rb +36 -0
  22. data/doc/examples/insert_bytea.rb +16 -0
  23. data/doc/examples/insert_bytea_array.rb +17 -0
  24. data/doc/examples/insert_default_values.rb +16 -0
  25. data/doc/examples/insert_insert.rb +16 -0
  26. data/doc/examples/insert_insert_default.rb +16 -0
  27. data/doc/examples/insert_insert_select.rb +20 -0
  28. data/doc/examples/insert_select.rb +20 -0
  29. data/doc/examples/interval.rb +17 -0
  30. data/doc/examples/savepoint.rb +38 -0
  31. data/doc/examples/select.rb +33 -0
  32. data/doc/examples/select2.rb +36 -0
  33. data/doc/examples/select_cross_join.rb +18 -0
  34. data/doc/examples/select_distinct.rb +18 -0
  35. data/doc/examples/select_distinct_on +19 -0
  36. data/doc/examples/select_for_update.rb +18 -0
  37. data/doc/examples/select_from.rb +17 -0
  38. data/doc/examples/select_from_subselect.rb +20 -0
  39. data/doc/examples/select_group_by.rb +19 -0
  40. data/doc/examples/select_having.rb +20 -0
  41. data/doc/examples/select_join_on.rb +18 -0
  42. data/doc/examples/select_join_using.rb +18 -0
  43. data/doc/examples/select_limit.rb +19 -0
  44. data/doc/examples/select_natural_join.rb +18 -0
  45. data/doc/examples/select_offset.rb +19 -0
  46. data/doc/examples/select_order_by.rb +20 -0
  47. data/doc/examples/select_select.rb +30 -0
  48. data/doc/examples/select_select_alias.rb +30 -0
  49. data/doc/examples/select_select_expression.rb +31 -0
  50. data/doc/examples/select_select_literal.rb +24 -0
  51. data/doc/examples/select_union.rb +21 -0
  52. data/doc/examples/select_where_array.rb +18 -0
  53. data/doc/examples/select_where_in.rb +18 -0
  54. data/doc/examples/select_where_string.rb +18 -0
  55. data/doc/examples/simple.rb +34 -0
  56. data/doc/examples/transaction.rb +30 -0
  57. data/doc/examples/transaction_abort.rb +30 -0
  58. data/doc/examples/transaction_commit.rb +34 -0
  59. data/doc/examples/translate_substitute_values.rb +17 -0
  60. data/doc/examples/update.rb +32 -0
  61. data/doc/examples/update2.rb +44 -0
  62. data/doc/examples/update_only.rb +17 -0
  63. data/doc/examples/update_set.rb +17 -0
  64. data/doc/examples/update_set_array.rb +16 -0
  65. data/doc/examples/update_set_bytea.rb +16 -0
  66. data/doc/examples/update_set_expression.rb +16 -0
  67. data/doc/examples/update_set_subselect.rb +20 -0
  68. data/doc/examples/update_where.rb +17 -0
  69. data/doc/examples/use_prefix.rb +8 -0
  70. data/doc/examples/use_prefix2.rb +11 -0
  71. data/doc/index.html +31 -0
  72. data/doc/insertexamples.rb +9 -0
  73. data/doc/makemanual +4 -0
  74. data/doc/makerdoc +5 -0
  75. data/doc/manual.dbk +622 -0
  76. data/lib/sqlpostgres/Connection.rb +198 -0
  77. data/lib/sqlpostgres/Cursor.rb +157 -0
  78. data/lib/sqlpostgres/Delete.rb +67 -0
  79. data/lib/sqlpostgres/Exceptions.rb +15 -0
  80. data/lib/sqlpostgres/Insert.rb +279 -0
  81. data/lib/sqlpostgres/NullConnection.rb +22 -0
  82. data/lib/sqlpostgres/PgBit.rb +73 -0
  83. data/lib/sqlpostgres/PgBox.rb +37 -0
  84. data/lib/sqlpostgres/PgCidr.rb +21 -0
  85. data/lib/sqlpostgres/PgCircle.rb +75 -0
  86. data/lib/sqlpostgres/PgInet.rb +21 -0
  87. data/lib/sqlpostgres/PgInterval.rb +208 -0
  88. data/lib/sqlpostgres/PgLineSegment.rb +37 -0
  89. data/lib/sqlpostgres/PgMacAddr.rb +21 -0
  90. data/lib/sqlpostgres/PgPath.rb +64 -0
  91. data/lib/sqlpostgres/PgPoint.rb +65 -0
  92. data/lib/sqlpostgres/PgPolygon.rb +56 -0
  93. data/lib/sqlpostgres/PgTime.rb +77 -0
  94. data/lib/sqlpostgres/PgTimeWithTimeZone.rb +98 -0
  95. data/lib/sqlpostgres/PgTimestamp.rb +93 -0
  96. data/lib/sqlpostgres/PgTwoPoints.rb +54 -0
  97. data/lib/sqlpostgres/PgType.rb +34 -0
  98. data/lib/sqlpostgres/PgWrapper.rb +41 -0
  99. data/lib/sqlpostgres/Savepoint.rb +98 -0
  100. data/lib/sqlpostgres/Select.rb +855 -0
  101. data/lib/sqlpostgres/Transaction.rb +120 -0
  102. data/lib/sqlpostgres/Translate.rb +436 -0
  103. data/lib/sqlpostgres/Update.rb +188 -0
  104. data/lib/sqlpostgres.rb +67 -0
  105. data/test/Assert.rb +72 -0
  106. data/test/Connection.test.rb +246 -0
  107. data/test/Cursor.test.rb +190 -0
  108. data/test/Delete.test.rb +68 -0
  109. data/test/Insert.test.rb +123 -0
  110. data/test/MockPGconn.rb +62 -0
  111. data/test/NullConnection.test.rb +32 -0
  112. data/test/PgBit.test.rb +98 -0
  113. data/test/PgBox.test.rb +108 -0
  114. data/test/PgCidr.test.rb +61 -0
  115. data/test/PgCircle.test.rb +107 -0
  116. data/test/PgInet.test.rb +61 -0
  117. data/test/PgInterval.test.rb +180 -0
  118. data/test/PgLineSegment.test.rb +108 -0
  119. data/test/PgMacAddr.test.rb +61 -0
  120. data/test/PgPath.test.rb +106 -0
  121. data/test/PgPoint.test.rb +100 -0
  122. data/test/PgPolygon.test.rb +95 -0
  123. data/test/PgTime.test.rb +120 -0
  124. data/test/PgTimeWithTimeZone.test.rb +117 -0
  125. data/test/PgTimestamp.test.rb +134 -0
  126. data/test/RandomThings.rb +25 -0
  127. data/test/Savepoint.test.rb +286 -0
  128. data/test/Select.test.rb +930 -0
  129. data/test/Test.rb +62 -0
  130. data/test/TestConfig.rb +21 -0
  131. data/test/TestSetup.rb +13 -0
  132. data/test/TestUtil.rb +92 -0
  133. data/test/Transaction.test.rb +275 -0
  134. data/test/Translate.test.rb +354 -0
  135. data/test/Update.test.rb +227 -0
  136. data/test/roundtrip.test.rb +565 -0
  137. data/test/test +34 -0
  138. data/tools/exampleinserter/ExampleInserter.rb +177 -0
  139. data/tools/rdoc/ChangeLog +796 -0
  140. data/tools/rdoc/EXAMPLE.rb +48 -0
  141. data/tools/rdoc/MANIFEST +58 -0
  142. data/tools/rdoc/Makefile +27 -0
  143. data/tools/rdoc/NEW_FEATURES +226 -0
  144. data/tools/rdoc/README +390 -0
  145. data/tools/rdoc/ToDo +6 -0
  146. data/tools/rdoc/contrib/Index +6 -0
  147. data/tools/rdoc/contrib/xslfo/ChangeLog +181 -0
  148. data/tools/rdoc/contrib/xslfo/README +106 -0
  149. data/tools/rdoc/contrib/xslfo/TODO +10 -0
  150. data/tools/rdoc/contrib/xslfo/convert.xsl +151 -0
  151. data/tools/rdoc/contrib/xslfo/demo/README +21 -0
  152. data/tools/rdoc/contrib/xslfo/demo/rdocfo +99 -0
  153. data/tools/rdoc/contrib/xslfo/fcm.xsl +54 -0
  154. data/tools/rdoc/contrib/xslfo/files.xsl +62 -0
  155. data/tools/rdoc/contrib/xslfo/labeled-lists.xsl +66 -0
  156. data/tools/rdoc/contrib/xslfo/lists.xsl +44 -0
  157. data/tools/rdoc/contrib/xslfo/modules.xsl +152 -0
  158. data/tools/rdoc/contrib/xslfo/rdoc.xsl +75 -0
  159. data/tools/rdoc/contrib/xslfo/source.xsl +66 -0
  160. data/tools/rdoc/contrib/xslfo/styles.xsl +69 -0
  161. data/tools/rdoc/contrib/xslfo/tables.xsl +67 -0
  162. data/tools/rdoc/contrib/xslfo/utils.xsl +21 -0
  163. data/tools/rdoc/debian/changelog +33 -0
  164. data/tools/rdoc/debian/compat +1 -0
  165. data/tools/rdoc/debian/control +20 -0
  166. data/tools/rdoc/debian/copyright +10 -0
  167. data/tools/rdoc/debian/dirs +2 -0
  168. data/tools/rdoc/debian/docs +2 -0
  169. data/tools/rdoc/debian/rdoc.1 +252 -0
  170. data/tools/rdoc/debian/rdoc.manpages +1 -0
  171. data/tools/rdoc/debian/rdoc.pod +149 -0
  172. data/tools/rdoc/debian/rules +9 -0
  173. data/tools/rdoc/dot/dot.rb +255 -0
  174. data/tools/rdoc/etc/rdoc.dtd +203 -0
  175. data/tools/rdoc/install.rb +137 -0
  176. data/tools/rdoc/markup/install.rb +43 -0
  177. data/tools/rdoc/markup/sample/sample.rb +42 -0
  178. data/tools/rdoc/markup/simple_markup/fragments.rb +323 -0
  179. data/tools/rdoc/markup/simple_markup/inline.rb +348 -0
  180. data/tools/rdoc/markup/simple_markup/lines.rb +147 -0
  181. data/tools/rdoc/markup/simple_markup/preprocess.rb +68 -0
  182. data/tools/rdoc/markup/simple_markup/to_html.rb +281 -0
  183. data/tools/rdoc/markup/simple_markup.rb +474 -0
  184. data/tools/rdoc/markup/test/AllTests.rb +2 -0
  185. data/tools/rdoc/markup/test/TestInline.rb +151 -0
  186. data/tools/rdoc/markup/test/TestParse.rb +411 -0
  187. data/tools/rdoc/rdoc/code_objects.rb +536 -0
  188. data/tools/rdoc/rdoc/diagram.rb +331 -0
  189. data/tools/rdoc/rdoc/generators/chm_generator.rb +112 -0
  190. data/tools/rdoc/rdoc/generators/html_generator.rb +1268 -0
  191. data/tools/rdoc/rdoc/generators/template/chm/chm.rb +86 -0
  192. data/tools/rdoc/rdoc/generators/template/html/html.rb +705 -0
  193. data/tools/rdoc/rdoc/generators/template/html/kilmer.rb +377 -0
  194. data/tools/rdoc/rdoc/generators/template/xml/rdf.rb +110 -0
  195. data/tools/rdoc/rdoc/generators/template/xml/xml.rb +110 -0
  196. data/tools/rdoc/rdoc/generators/xml_generator.rb +130 -0
  197. data/tools/rdoc/rdoc/options.rb +451 -0
  198. data/tools/rdoc/rdoc/parsers/parse_c.rb +287 -0
  199. data/tools/rdoc/rdoc/parsers/parse_f95.rb +118 -0
  200. data/tools/rdoc/rdoc/parsers/parse_rb.rb +2311 -0
  201. data/tools/rdoc/rdoc/parsers/parse_simple.rb +37 -0
  202. data/tools/rdoc/rdoc/parsers/parserfactory.rb +75 -0
  203. data/tools/rdoc/rdoc/rdoc.rb +219 -0
  204. data/tools/rdoc/rdoc/template.rb +234 -0
  205. data/tools/rdoc/rdoc/tokenstream.rb +25 -0
  206. data/tools/rdoc/rdoc.rb +9 -0
  207. metadata +291 -0
@@ -0,0 +1,37 @@
1
+ # Parse a non-source file. We basically take the whole thing
2
+ # as one big comment. If the first character in the file
3
+ # is '#', we strip leading pound signs.
4
+
5
+
6
+ require "rdoc/code_objects"
7
+ require "markup/simple_markup/preprocess"
8
+
9
+ module RDoc
10
+ # See rdoc/parsers/parse_c.rb
11
+
12
+ class SimpleParser
13
+
14
+ # prepare to parse a plain file
15
+ def initialize(top_level, file_name, body, options)
16
+
17
+ preprocess = SM::PreProcess.new(file_name, options.rdoc_include)
18
+
19
+ preprocess.handle(body) do |directive, param|
20
+ $stderr.puts "Unrecognized directive '#{directive}' in #{file_name}"
21
+ end
22
+
23
+ @body = body
24
+ @options = options
25
+ @top_level = top_level
26
+ end
27
+
28
+ # Extract the file contents and attach them to the toplevel as a
29
+ # comment
30
+
31
+ def scan
32
+ # @body.gsub(/^(\s\n)+/, '')
33
+ @top_level.comment = @body
34
+ @top_level
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,75 @@
1
+ require "rdoc/parsers/parse_simple"
2
+
3
+ module RDoc
4
+
5
+ # A parser is simple a class that implements
6
+ #
7
+ # #initialize(file_name, body, options)
8
+ #
9
+ # and
10
+ #
11
+ # #scan
12
+ #
13
+ # The initialize method takes a file name to be used, the body of the
14
+ # file, and an RDoc::Options object. The scan method is then called
15
+ # to return an appropriately parsed TopLevel code object.
16
+
17
+ # The ParseFactory is used to redirect to the correct parser given a filename
18
+ # extension. This magic works because individual parsers have to register
19
+ # themselves with us as they are loaded in. The do this using the following
20
+ # incantation
21
+ #
22
+ #
23
+ # require "rdoc/parsers/parsefactory"
24
+ #
25
+ # module RDoc
26
+ #
27
+ # class XyzParser
28
+ # extend ParseFactory <<<<
29
+ # parse_files_matching /\.xyz$/ <<<<
30
+ #
31
+ # def initialize(file_name, body, options)
32
+ # ...
33
+ # end
34
+ #
35
+ # def scan
36
+ # ...
37
+ # end
38
+ # end
39
+ # end
40
+
41
+
42
+
43
+ module ParserFactory
44
+
45
+ @@parsers = []
46
+
47
+ Parsers = Struct.new(:regexp, :parser)
48
+
49
+ # Record the fact that a particular class parses files that
50
+ # match a given extension
51
+
52
+ def parse_files_matching(regexp)
53
+ @@parsers.unshift Parsers.new(regexp, self)
54
+ end
55
+
56
+ # Return a parser that can handle a particular extension
57
+
58
+ def ParserFactory.can_parse(file_name)
59
+ @@parsers.find {|p| p.regexp.match(file_name) }
60
+ end
61
+
62
+ # Find the correct parser for a particular file name. Return a
63
+ # SimpleParser for ones that we don't know
64
+
65
+ def ParserFactory.parser_for(top_level, file_name, body, options)
66
+ parser_description = can_parse(file_name)
67
+ if parser_description
68
+ parser = parser_description.parser
69
+ else
70
+ parser = SimpleParser
71
+ end
72
+ parser.new(top_level, file_name, body, options)
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,219 @@
1
+ # See README.
2
+ #
3
+
4
+
5
+ RDOC_VERSION = "0.9.0"
6
+
7
+ rcs = '$Date: 2003/10/10 08:00:05 $ $Revision: 1.1 $'.
8
+ gsub(/\$/, '').
9
+ sub(/Date: /, ': ').
10
+ sub(/ Revision: (\S+)/) { "(#$1)" }
11
+
12
+ VERSION_STRING = %{RDoc V} + RDOC_VERSION + rcs
13
+
14
+
15
+ require 'rdoc/parsers/parse_rb.rb'
16
+ require 'rdoc/parsers/parse_c.rb'
17
+ require 'rdoc/parsers/parse_f95.rb'
18
+
19
+ require 'rdoc/parsers/parse_simple.rb'
20
+ require 'rdoc/options'
21
+
22
+ require 'rdoc/diagram'
23
+
24
+ require 'find'
25
+ require 'ftools'
26
+
27
+ # We put rdoc stuff in the RDoc module to avoid namespace
28
+ # clutter.
29
+ #
30
+ # ToDo: This isn't universally true.
31
+
32
+ module RDoc
33
+
34
+ # Exception thrown by any rdoc error. Only the #message part is
35
+ # of use externally.
36
+
37
+ class RDocError < Exception
38
+ end
39
+
40
+ # Encapsulate the production of rdoc documentation. Basically
41
+ # you can use this as you would invoke rdoc from the command
42
+ # line:
43
+ #
44
+ # rdoc = RDoc::RDoc.new
45
+ # rdoc.document(args)
46
+ #
47
+ # where _args_ is an array of strings, each corresponding to
48
+ # an argument you'd give rdoc on the command line. See rdoc/rdoc.rb
49
+ # for details.
50
+
51
+ class RDoc
52
+
53
+ ##
54
+ # This is the list of output generators that we
55
+ # support
56
+
57
+ Generator = Struct.new(:file_name, :class_name, :key)
58
+
59
+ GENERATORS = {}
60
+ $:.collect {|d|
61
+ File::expand_path(d)
62
+ }.find_all {|d|
63
+ File::directory?("#{d}/rdoc/generators")
64
+ }.each {|dir|
65
+ Dir::entries("#{dir}/rdoc/generators").each {|gen|
66
+ next unless /(\w+)_generator.rb$/ =~ gen
67
+ type = $1
68
+ unless GENERATORS.has_key? type
69
+ GENERATORS[type] = Generator.new("rdoc/generators/#{gen}",
70
+ "#{type.upcase}Generator".intern,
71
+ type)
72
+ end
73
+ }
74
+ }
75
+
76
+ #######
77
+ private
78
+ #######
79
+
80
+ ##
81
+ # Report an error message and exit
82
+
83
+ def error(msg)
84
+ raise RDocError.new(msg)
85
+ end
86
+
87
+ ##
88
+ # Create an output dir if it doesn't exist. If it does
89
+ # exist, but doesn't contain the flag file <tt>created.rid</tt>
90
+ # then we refuse to use it, as we may clobber some
91
+ # manually generated documentation
92
+
93
+ def setup_output_dir(op_dir)
94
+ flag_file = File.join(op_dir, "created.rid")
95
+ if File.exist?(op_dir)
96
+ unless File.directory?(op_dir)
97
+ error "'#{op_dir}' exists, and is not a directory"
98
+ end
99
+ unless File.file?(flag_file)
100
+ error "\nDirectory #{op_dir} already exists, but it looks like it\n" +
101
+ "isn't an RDoc directory. Because RDoc doesn't want to risk\n" +
102
+ "destroying any of your existing files, you'll need to\n" +
103
+ "specify a different output directory name (using the\n" +
104
+ "--op <dir> option).\n\n"
105
+ end
106
+ else
107
+ File.makedirs(op_dir)
108
+ end
109
+ File.open(flag_file, "w") {|f| f.puts Time.now }
110
+ end
111
+
112
+
113
+ # Given a list of files and directories, create a list
114
+ # of all the Ruby files they contain.
115
+
116
+ def normalized_file_list(options, *relative_files)
117
+ file_list = []
118
+
119
+ relative_files.each do |rel_file_name|
120
+
121
+ case type = File.ftype(rel_file_name)
122
+ when "file"
123
+ file_list << rel_file_name
124
+ when "directory"
125
+ next if options.exclude && options.exclude =~ rel_file_name
126
+ Find.find(rel_file_name) do |fn|
127
+ next if options.exclude && options.exclude =~ fn
128
+ next unless ParserFactory.can_parse(fn)
129
+ next unless File.file?(fn)
130
+
131
+ file_list << fn.sub(%r{\./}, '')
132
+ end
133
+ else
134
+ raise RDocError.new("I can't deal with a #{type} #{rel_file_name}")
135
+ end
136
+ end
137
+ file_list
138
+ end
139
+
140
+ # Parse each file on the command line, recursively entering
141
+ # directories
142
+
143
+ def parse_files(options)
144
+
145
+ file_info = []
146
+
147
+ files = options.files
148
+ files = ["."] if files.empty?
149
+
150
+ file_list = normalized_file_list(options, *files)
151
+
152
+ file_list.each do |fn|
153
+ $stderr.printf("\n%35s: ", File.basename(fn)) unless options.quiet
154
+
155
+ content = File.open(fn, "r") {|f| f.read}
156
+
157
+ top_level = TopLevel.new(fn)
158
+ parser = ParserFactory.parser_for(top_level, fn, content, options)
159
+ file_info << parser.scan
160
+ end
161
+
162
+ file_info
163
+ end
164
+
165
+
166
+ public
167
+
168
+ ###################################################################
169
+ #
170
+ # Format up one or more files according to the given arguments.
171
+ # For simplicity, _argv_ is an array of strings, equivalent to the
172
+ # strings that would be passed on the command line. (This isn't a
173
+ # coincidence, as we _do_ pass in ARGV when running
174
+ # interactively). For a list of options, see rdoc/rdoc.rb. By
175
+ # default, output will be stored in a directory called +doc+ below
176
+ # the current directory, so make sure you're somewhere writable
177
+ # before invoking.
178
+ #
179
+ # Throws: RDocError on error
180
+
181
+ def document(argv)
182
+
183
+ TopLevel::reset
184
+
185
+ options = Options.instance
186
+ options.parse(argv, GENERATORS)
187
+
188
+ file_info = parse_files(options)
189
+
190
+ gen = options.generator
191
+
192
+ $stderr.puts "\nGenerating #{gen.key.upcase}..." unless options.quiet
193
+
194
+ require gen.file_name
195
+
196
+ gen_class = Generators.const_get(gen.class_name)
197
+
198
+ unless file_info.empty?
199
+ gen = gen_class.for(options)
200
+
201
+ pwd = Dir.pwd
202
+
203
+ unless options.all_one_file
204
+ setup_output_dir(options.op_dir)
205
+ Dir.chdir(options.op_dir)
206
+ end
207
+
208
+ begin
209
+ Diagram.new(file_info, options).draw if options.diagram
210
+ gen.generate(file_info)
211
+ ensure
212
+ Dir.chdir(pwd)
213
+ end
214
+
215
+ end
216
+ end
217
+ end
218
+ end
219
+
@@ -0,0 +1,234 @@
1
+ # Cheap-n-cheerful HTML page template system. You create a
2
+ # template containing:
3
+ #
4
+ # * variable names between percent signs (<tt>%fred%</tt>)
5
+ # * blocks of repeating stuff:
6
+ #
7
+ # START:key
8
+ # ... stuff
9
+ # END:key
10
+ #
11
+ # You feed the code a hash. For simple variables, the values
12
+ # are resolved directly from the hash. For blocks, the hash entry
13
+ # corresponding to +key+ will be an array of hashes. The block will
14
+ # be generated once for each entry. Blocks can be nested arbitrarily
15
+ # deeply.
16
+ #
17
+ # The template may also contain
18
+ #
19
+ # IF:key
20
+ # ... stuff
21
+ # ENDIF:key
22
+ #
23
+ # _stuff_ will only be included in the output if the corresponding
24
+ # key is set in the value hash.
25
+ #
26
+ # Usage: Given a set of templates <tt>T1, T2,</tt> etc
27
+ #
28
+ # values = { "name" => "Dave", state => "TX" }
29
+ #
30
+ # t = TemplatePage.new(T1, T2, T3)
31
+ # File.open(name, "w") {|f| t.write_html_on(f, values)}
32
+ # or
33
+ # res = ''
34
+ # t.write_html_on(res, values)
35
+ #
36
+ #
37
+
38
+ class TemplatePage
39
+
40
+ ##########
41
+ # A context holds a stack of key/value pairs (like a symbol
42
+ # table). When asked to resolve a key, it first searches the top of
43
+ # the stack, then the next level, and so on until it finds a match
44
+ # (or runs out of entries)
45
+
46
+ class Context
47
+ def initialize
48
+ @stack = []
49
+ end
50
+
51
+ def push(hash)
52
+ @stack.push(hash)
53
+ end
54
+
55
+ def pop
56
+ @stack.pop
57
+ end
58
+
59
+ # Find a scalar value, throwing an exception if not found. This
60
+ # method is used when substituting the %xxx% constructs
61
+
62
+ def find_scalar(key)
63
+ @stack.reverse_each do |level|
64
+ if val = level[key]
65
+ return val unless val.kind_of? Array
66
+ end
67
+ end
68
+ raise "Template error: can't find variable '#{key}'"
69
+ end
70
+
71
+ # Lookup any key in the stack of hashes
72
+
73
+ def lookup(key)
74
+ @stack.reverse_each do |level|
75
+ val = level[key]
76
+ return val if val
77
+ end
78
+ nil
79
+ end
80
+ end
81
+
82
+ #########
83
+ # Simple class to read lines out of a string
84
+
85
+ class LineReader
86
+ # we're initialized with an array of lines
87
+ def initialize(lines)
88
+ @lines = lines
89
+ end
90
+
91
+ # read the next line
92
+ def read
93
+ @lines.shift
94
+ end
95
+
96
+ # Return a list of lines up to the line that matches
97
+ # a pattern. That last line is discarded.
98
+ def read_up_to(pattern)
99
+ res = []
100
+ while line = read
101
+ if pattern.match(line)
102
+ return LineReader.new(res)
103
+ else
104
+ res << line
105
+ end
106
+ end
107
+ raise "Missing end tag in template: #{pattern.source}"
108
+ end
109
+
110
+ # Return a copy of ourselves that can be modified without
111
+ # affecting us
112
+ def dup
113
+ LineReader.new(@lines.dup)
114
+ end
115
+ end
116
+
117
+
118
+
119
+ # +templates+ is an array of strings containing the templates.
120
+ # We start at the first, and substitute in subsequent ones
121
+ # where the string <tt>!INCLUDE!</tt> occurs. For example,
122
+ # we could have the overall page template containing
123
+ #
124
+ # <html><body>
125
+ # <h1>Master</h1>
126
+ # !INCLUDE!
127
+ # </bost></html>
128
+ #
129
+ # and substitute subpages in to it by passing [master, sub_page].
130
+ # This gives us a cheap way of framing pages
131
+
132
+ def initialize(*templates)
133
+ result = "!INCLUDE!"
134
+ templates.each do |content|
135
+ result.sub!(/!INCLUDE!/, content)
136
+ end
137
+ @lines = LineReader.new(result.split($/))
138
+ end
139
+
140
+ # Render the templates into HTML, storing the result on +op+
141
+ # using the method <tt><<</tt>. The <tt>value_hash</tt> contains
142
+ # key/value pairs used to drive the substitution (as described above)
143
+
144
+ def write_html_on(op, value_hash)
145
+ @context = Context.new
146
+ op << substitute_into(@lines, value_hash).tr("\000", '\\')
147
+ end
148
+
149
+
150
+ # Substitute a set of key/value pairs into the given template.
151
+ # Keys with scalar values have them substituted directly into
152
+ # the page. Those with array values invoke <tt>substitute_array</tt>
153
+ # (below), which examples a block of the template once for each
154
+ # row in the array.
155
+ #
156
+ # This routine also copes with the <tt>IF:</tt>_key_ directive,
157
+ # removing chunks of the template if the corresponding key
158
+ # does not appear in the hash, and the START: directive, which
159
+ # loops its contents for each value in an array
160
+
161
+ def substitute_into(lines, values)
162
+ @context.push(values)
163
+ skip_to = nil
164
+ result = []
165
+
166
+ while line = lines.read
167
+
168
+ case line
169
+
170
+ when /^IF:(\w+)/
171
+ lines.read_up_to(/^ENDIF:#$1/) unless @context.lookup($1)
172
+
173
+ when /^IFNOT:(\w+)/
174
+ lines.read_up_to(/^ENDIF:#$1/) if @context.lookup($1)
175
+
176
+ when /^ENDIF:/
177
+ ;
178
+
179
+ when /^START:(\w+)/
180
+ tag = $1
181
+ body = lines.read_up_to(/^END:#{tag}/)
182
+ inner_values = @context.lookup(tag)
183
+ raise "unknown tag: #{tag}" unless inner_values
184
+ raise "not array: #{tag}" unless inner_values.kind_of?(Array)
185
+ inner_values.each do |vals|
186
+ result << substitute_into(body.dup, vals)
187
+ end
188
+ else
189
+ result << expand_line(line.dup)
190
+ end
191
+ end
192
+
193
+ @context.pop
194
+
195
+ result.join("\n")
196
+ end
197
+
198
+ # Given an individual line, we look for %xxx% constructs and
199
+ # HREF:ref:name: constructs, substituting for each.
200
+
201
+ def expand_line(line)
202
+ # Generate a cross reference if a reference is given,
203
+ # otherwise just fill in the name part
204
+
205
+ line.gsub!(/HREF:(\w+?):(\w+?):/) {
206
+ ref = @context.lookup($1)
207
+ name = @context.find_scalar($2)
208
+
209
+ if ref and !ref.kind_of?(Array)
210
+ "<a href=\"#{ref}\">#{name}</a>"
211
+ else
212
+ name
213
+ end
214
+ }
215
+
216
+ # Substitute in values for %xxx% constructs. This is made complex
217
+ # because the replacement string may contain characters that are
218
+ # meaningful to the regexp (like \1)
219
+
220
+ line = line.gsub(/%([a-zA-Z]\w*)%/) {
221
+ val = @context.find_scalar($1)
222
+ val.tr('\\', "\000")
223
+ }
224
+
225
+
226
+ line
227
+ rescue Exception => e
228
+ $stderr.puts "Error in template: #{e}"
229
+ $stderr.puts "Original line: #{line}"
230
+ exit
231
+ end
232
+
233
+ end
234
+
@@ -0,0 +1,25 @@
1
+ # A TokenStream is a list of tokens, gathered during the parse
2
+ # of some entity (say a method). Entities populate these streams
3
+ # by being registered with the lexer. Any class can collect tokens
4
+ # by including TokenStream. From the outside, you use such an object
5
+ # by calling the start_collecting_tokens method, followed by calls
6
+ # to add_token and pop_token
7
+
8
+ module TokenStream
9
+ def token_stream
10
+ @token_stream
11
+ end
12
+
13
+ def start_collecting_tokens
14
+ @token_stream = []
15
+ end
16
+ def add_token(tk)
17
+ @token_stream << tk
18
+ end
19
+ def add_tokens(tks)
20
+ tks.each {|tk| add_token(tk)}
21
+ end
22
+ def pop_token
23
+ @token_stream.pop
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ require 'rdoc/rdoc'
2
+
3
+ begin
4
+ r = RDoc::RDoc.new
5
+ r.document(ARGV)
6
+ rescue RDoc::RDocError => e
7
+ $stderr.puts e.message
8
+ exit(1)
9
+ end