sqlite3-full 1.3.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/API_CHANGES.rdoc +50 -0
  4. data/CHANGELOG.rdoc +278 -0
  5. data/ChangeLog.cvs +88 -0
  6. data/Gemfile +15 -0
  7. data/LICENSE +34 -0
  8. data/Manifest.txt +52 -0
  9. data/README.rdoc +90 -0
  10. data/Rakefile +10 -0
  11. data/ext/sqlite3/backup.c +168 -0
  12. data/ext/sqlite3/backup.h +15 -0
  13. data/ext/sqlite3/database.c +825 -0
  14. data/ext/sqlite3/database.h +15 -0
  15. data/ext/sqlite3/exception.c +94 -0
  16. data/ext/sqlite3/exception.h +8 -0
  17. data/ext/sqlite3/extconf.rb +86 -0
  18. data/ext/sqlite3/sqlite3.c +97 -0
  19. data/ext/sqlite3/sqlite3_ruby.h +52 -0
  20. data/ext/sqlite3/sqlite3amalgamation.c +153367 -0
  21. data/ext/sqlite3/statement.c +447 -0
  22. data/ext/sqlite3/statement.h +16 -0
  23. data/faq/faq.rb +145 -0
  24. data/faq/faq.yml +426 -0
  25. data/lib/sqlite3/constants.rb +49 -0
  26. data/lib/sqlite3/database.rb +590 -0
  27. data/lib/sqlite3/errors.rb +44 -0
  28. data/lib/sqlite3/pragmas.rb +280 -0
  29. data/lib/sqlite3/resultset.rb +195 -0
  30. data/lib/sqlite3/statement.rb +144 -0
  31. data/lib/sqlite3/translator.rb +118 -0
  32. data/lib/sqlite3/value.rb +57 -0
  33. data/lib/sqlite3/version.rb +25 -0
  34. data/lib/sqlite3.rb +10 -0
  35. data/setup.rb +1333 -0
  36. data/tasks/faq.rake +9 -0
  37. data/tasks/gem.rake +38 -0
  38. data/tasks/native.rake +52 -0
  39. data/tasks/vendor_sqlite3.rake +91 -0
  40. data/test/helper.rb +18 -0
  41. data/test/test_backup.rb +33 -0
  42. data/test/test_collation.rb +82 -0
  43. data/test/test_database.rb +367 -0
  44. data/test/test_database_readonly.rb +29 -0
  45. data/test/test_deprecated.rb +44 -0
  46. data/test/test_encoding.rb +153 -0
  47. data/test/test_integration.rb +572 -0
  48. data/test/test_integration_open_close.rb +30 -0
  49. data/test/test_integration_pending.rb +115 -0
  50. data/test/test_integration_resultset.rb +159 -0
  51. data/test/test_integration_statement.rb +194 -0
  52. data/test/test_result_set.rb +37 -0
  53. data/test/test_sqlite3.rb +9 -0
  54. data/test/test_statement.rb +260 -0
  55. data/test/test_statement_execute.rb +35 -0
  56. metadata +205 -0
@@ -0,0 +1,447 @@
1
+ #include <sqlite3_ruby.h>
2
+
3
+ #define REQUIRE_OPEN_STMT(_ctxt) \
4
+ if(!_ctxt->st) \
5
+ rb_raise(rb_path2class("SQLite3::Exception"), "cannot use a closed statement");
6
+
7
+ VALUE cSqlite3Statement;
8
+
9
+ static void deallocate(void * ctx)
10
+ {
11
+ sqlite3StmtRubyPtr c = (sqlite3StmtRubyPtr)ctx;
12
+ xfree(c);
13
+ }
14
+
15
+ static VALUE allocate(VALUE klass)
16
+ {
17
+ sqlite3StmtRubyPtr ctx = xcalloc((size_t)1, sizeof(sqlite3StmtRuby));
18
+ ctx->st = NULL;
19
+ ctx->done_p = 0;
20
+
21
+ return Data_Wrap_Struct(klass, NULL, deallocate, ctx);
22
+ }
23
+
24
+ /* call-seq: SQLite3::Statement.new(db, sql)
25
+ *
26
+ * Create a new statement attached to the given Database instance, and which
27
+ * encapsulates the given SQL text. If the text contains more than one
28
+ * statement (i.e., separated by semicolons), then the #remainder property
29
+ * will be set to the trailing text.
30
+ */
31
+ static VALUE initialize(VALUE self, VALUE db, VALUE sql)
32
+ {
33
+ sqlite3RubyPtr db_ctx;
34
+ sqlite3StmtRubyPtr ctx;
35
+ const char *tail = NULL;
36
+ int status;
37
+
38
+ StringValue(sql);
39
+
40
+ Data_Get_Struct(db, sqlite3Ruby, db_ctx);
41
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
42
+
43
+ if(!db_ctx->db)
44
+ rb_raise(rb_eArgError, "prepare called on a closed database");
45
+
46
+ #ifdef HAVE_RUBY_ENCODING_H
47
+ if(!UTF8_P(sql)) {
48
+ sql = rb_str_export_to_enc(sql, rb_utf8_encoding());
49
+ }
50
+ #endif
51
+
52
+ #ifdef HAVE_SQLITE3_PREPARE_V2
53
+ status = sqlite3_prepare_v2(
54
+ #else
55
+ status = sqlite3_prepare(
56
+ #endif
57
+ db_ctx->db,
58
+ (const char *)StringValuePtr(sql),
59
+ (int)RSTRING_LEN(sql),
60
+ &ctx->st,
61
+ &tail
62
+ );
63
+
64
+ CHECK(db_ctx->db, status);
65
+
66
+ rb_iv_set(self, "@connection", db);
67
+ rb_iv_set(self, "@remainder", rb_str_new2(tail));
68
+ rb_iv_set(self, "@columns", Qnil);
69
+ rb_iv_set(self, "@types", Qnil);
70
+
71
+ return self;
72
+ }
73
+
74
+ /* call-seq: stmt.close
75
+ *
76
+ * Closes the statement by finalizing the underlying statement
77
+ * handle. The statement must not be used after being closed.
78
+ */
79
+ static VALUE sqlite3_rb_close(VALUE self)
80
+ {
81
+ sqlite3StmtRubyPtr ctx;
82
+
83
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
84
+
85
+ REQUIRE_OPEN_STMT(ctx);
86
+
87
+ sqlite3_finalize(ctx->st);
88
+ ctx->st = NULL;
89
+
90
+ return self;
91
+ }
92
+
93
+ /* call-seq: stmt.closed?
94
+ *
95
+ * Returns true if the statement has been closed.
96
+ */
97
+ static VALUE closed_p(VALUE self)
98
+ {
99
+ sqlite3StmtRubyPtr ctx;
100
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
101
+
102
+ if(!ctx->st) return Qtrue;
103
+
104
+ return Qfalse;
105
+ }
106
+
107
+ static VALUE step(VALUE self)
108
+ {
109
+ sqlite3StmtRubyPtr ctx;
110
+ sqlite3_stmt *stmt;
111
+ int value, length;
112
+ VALUE list;
113
+ #ifdef HAVE_RUBY_ENCODING_H
114
+ rb_encoding * internal_encoding;
115
+ #endif
116
+
117
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
118
+
119
+ REQUIRE_OPEN_STMT(ctx);
120
+
121
+ if(ctx->done_p) return Qnil;
122
+
123
+ #ifdef HAVE_RUBY_ENCODING_H
124
+ {
125
+ VALUE db = rb_iv_get(self, "@connection");
126
+ rb_funcall(db, rb_intern("encoding"), 0);
127
+ internal_encoding = rb_default_internal_encoding();
128
+ }
129
+ #endif
130
+
131
+ stmt = ctx->st;
132
+
133
+ value = sqlite3_step(stmt);
134
+ length = sqlite3_column_count(stmt);
135
+ list = rb_ary_new2((long)length);
136
+
137
+ switch(value) {
138
+ case SQLITE_ROW:
139
+ {
140
+ int i;
141
+ for(i = 0; i < length; i++) {
142
+ switch(sqlite3_column_type(stmt, i)) {
143
+ case SQLITE_INTEGER:
144
+ rb_ary_push(list, LL2NUM(sqlite3_column_int64(stmt, i)));
145
+ break;
146
+ case SQLITE_FLOAT:
147
+ rb_ary_push(list, rb_float_new(sqlite3_column_double(stmt, i)));
148
+ break;
149
+ case SQLITE_TEXT:
150
+ {
151
+ VALUE str = rb_tainted_str_new(
152
+ (const char *)sqlite3_column_text(stmt, i),
153
+ (long)sqlite3_column_bytes(stmt, i)
154
+ );
155
+ #ifdef HAVE_RUBY_ENCODING_H
156
+ rb_enc_associate_index(str, rb_utf8_encindex());
157
+ if(internal_encoding)
158
+ str = rb_str_export_to_enc(str, internal_encoding);
159
+ #endif
160
+ rb_ary_push(list, str);
161
+ }
162
+ break;
163
+ case SQLITE_BLOB:
164
+ {
165
+ VALUE str = rb_tainted_str_new(
166
+ (const char *)sqlite3_column_blob(stmt, i),
167
+ (long)sqlite3_column_bytes(stmt, i)
168
+ );
169
+ rb_ary_push(list, str);
170
+ }
171
+ break;
172
+ case SQLITE_NULL:
173
+ rb_ary_push(list, Qnil);
174
+ break;
175
+ default:
176
+ rb_raise(rb_eRuntimeError, "bad type");
177
+ }
178
+ }
179
+ }
180
+ break;
181
+ case SQLITE_DONE:
182
+ ctx->done_p = 1;
183
+ return Qnil;
184
+ break;
185
+ default:
186
+ sqlite3_reset(stmt);
187
+ ctx->done_p = 0;
188
+ CHECK(sqlite3_db_handle(ctx->st), value);
189
+ }
190
+
191
+ return list;
192
+ }
193
+
194
+ /* call-seq: stmt.bind_param(key, value)
195
+ *
196
+ * Binds value to the named (or positional) placeholder. If +param+ is a
197
+ * Fixnum, it is treated as an index for a positional placeholder.
198
+ * Otherwise it is used as the name of the placeholder to bind to.
199
+ *
200
+ * See also #bind_params.
201
+ */
202
+ static VALUE bind_param(VALUE self, VALUE key, VALUE value)
203
+ {
204
+ sqlite3StmtRubyPtr ctx;
205
+ int status;
206
+ int index;
207
+
208
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
209
+ REQUIRE_OPEN_STMT(ctx);
210
+
211
+ switch(TYPE(key)) {
212
+ case T_SYMBOL:
213
+ key = rb_funcall(key, rb_intern("to_s"), 0);
214
+ case T_STRING:
215
+ if(RSTRING_PTR(key)[0] != ':') key = rb_str_plus(rb_str_new2(":"), key);
216
+ index = sqlite3_bind_parameter_index(ctx->st, StringValuePtr(key));
217
+ break;
218
+ default:
219
+ index = (int)NUM2INT(key);
220
+ }
221
+
222
+ if(index == 0)
223
+ rb_raise(rb_path2class("SQLite3::Exception"), "no such bind parameter");
224
+
225
+ switch(TYPE(value)) {
226
+ case T_STRING:
227
+ if(CLASS_OF(value) == cSqlite3Blob
228
+ #ifdef HAVE_RUBY_ENCODING_H
229
+ || rb_enc_get_index(value) == rb_ascii8bit_encindex()
230
+ #endif
231
+ ) {
232
+ status = sqlite3_bind_blob(
233
+ ctx->st,
234
+ index,
235
+ (const char *)StringValuePtr(value),
236
+ (int)RSTRING_LEN(value),
237
+ SQLITE_TRANSIENT
238
+ );
239
+ } else {
240
+
241
+
242
+ #ifdef HAVE_RUBY_ENCODING_H
243
+ if (UTF16_LE_P(value)) {
244
+ status = sqlite3_bind_text16(
245
+ ctx->st,
246
+ index,
247
+ (const char *)StringValuePtr(value),
248
+ (int)RSTRING_LEN(value),
249
+ SQLITE_TRANSIENT
250
+ );
251
+ } else {
252
+ if (!UTF8_P(value) || !USASCII_P(value)) {
253
+ value = rb_str_encode(value, rb_enc_from_encoding(rb_utf8_encoding()), 0, Qnil);
254
+ }
255
+ #endif
256
+ status = sqlite3_bind_text(
257
+ ctx->st,
258
+ index,
259
+ (const char *)StringValuePtr(value),
260
+ (int)RSTRING_LEN(value),
261
+ SQLITE_TRANSIENT
262
+ );
263
+ #ifdef HAVE_RUBY_ENCODING_H
264
+ }
265
+ #endif
266
+ }
267
+ break;
268
+ case T_BIGNUM: {
269
+ sqlite3_int64 num64;
270
+ if (bignum_to_int64(value, &num64)) {
271
+ status = sqlite3_bind_int64(ctx->st, index, num64);
272
+ break;
273
+ }
274
+ }
275
+ case T_FLOAT:
276
+ status = sqlite3_bind_double(ctx->st, index, NUM2DBL(value));
277
+ break;
278
+ case T_FIXNUM:
279
+ status = sqlite3_bind_int64(ctx->st, index, (sqlite3_int64)FIX2LONG(value));
280
+ break;
281
+ case T_NIL:
282
+ status = sqlite3_bind_null(ctx->st, index);
283
+ break;
284
+ default:
285
+ rb_raise(rb_eRuntimeError, "can't prepare %s",
286
+ rb_class2name(CLASS_OF(value)));
287
+ break;
288
+ }
289
+
290
+ CHECK(sqlite3_db_handle(ctx->st), status);
291
+
292
+ return self;
293
+ }
294
+
295
+ /* call-seq: stmt.reset!
296
+ *
297
+ * Resets the statement. This is typically done internally, though it might
298
+ * occassionally be necessary to manually reset the statement.
299
+ */
300
+ static VALUE reset_bang(VALUE self)
301
+ {
302
+ sqlite3StmtRubyPtr ctx;
303
+
304
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
305
+ REQUIRE_OPEN_STMT(ctx);
306
+
307
+ sqlite3_reset(ctx->st);
308
+
309
+ ctx->done_p = 0;
310
+
311
+ return self;
312
+ }
313
+
314
+ /* call-seq: stmt.clear_bindings!
315
+ *
316
+ * Resets the statement. This is typically done internally, though it might
317
+ * occassionally be necessary to manually reset the statement.
318
+ */
319
+ static VALUE clear_bindings(VALUE self)
320
+ {
321
+ sqlite3StmtRubyPtr ctx;
322
+
323
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
324
+ REQUIRE_OPEN_STMT(ctx);
325
+
326
+ sqlite3_clear_bindings(ctx->st);
327
+
328
+ ctx->done_p = 0;
329
+
330
+ return self;
331
+ }
332
+
333
+ /* call-seq: stmt.done?
334
+ *
335
+ * returns true if all rows have been returned.
336
+ */
337
+ static VALUE done_p(VALUE self)
338
+ {
339
+ sqlite3StmtRubyPtr ctx;
340
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
341
+
342
+ if(ctx->done_p) return Qtrue;
343
+ return Qfalse;
344
+ }
345
+
346
+ /* call-seq: stmt.column_count
347
+ *
348
+ * Returns the number of columns to be returned for this statement
349
+ */
350
+ static VALUE column_count(VALUE self)
351
+ {
352
+ sqlite3StmtRubyPtr ctx;
353
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
354
+ REQUIRE_OPEN_STMT(ctx);
355
+
356
+ return INT2NUM((long)sqlite3_column_count(ctx->st));
357
+ }
358
+
359
+ /* call-seq: stmt.column_name(index)
360
+ *
361
+ * Get the column name at +index+. 0 based.
362
+ */
363
+ static VALUE column_name(VALUE self, VALUE index)
364
+ {
365
+ sqlite3StmtRubyPtr ctx;
366
+ const char * name;
367
+
368
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
369
+ REQUIRE_OPEN_STMT(ctx);
370
+
371
+ name = sqlite3_column_name(ctx->st, (int)NUM2INT(index));
372
+
373
+ if(name) return SQLITE3_UTF8_STR_NEW2(name);
374
+ return Qnil;
375
+ }
376
+
377
+ /* call-seq: stmt.column_decltype(index)
378
+ *
379
+ * Get the column type at +index+. 0 based.
380
+ */
381
+ static VALUE column_decltype(VALUE self, VALUE index)
382
+ {
383
+ sqlite3StmtRubyPtr ctx;
384
+ const char * name;
385
+
386
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
387
+ REQUIRE_OPEN_STMT(ctx);
388
+
389
+ name = sqlite3_column_decltype(ctx->st, (int)NUM2INT(index));
390
+
391
+ if(name) return rb_str_new2(name);
392
+ return Qnil;
393
+ }
394
+
395
+ /* call-seq: stmt.bind_parameter_count
396
+ *
397
+ * Return the number of bind parameters
398
+ */
399
+ static VALUE bind_parameter_count(VALUE self)
400
+ {
401
+ sqlite3StmtRubyPtr ctx;
402
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
403
+ REQUIRE_OPEN_STMT(ctx);
404
+
405
+ return INT2NUM((long)sqlite3_bind_parameter_count(ctx->st));
406
+ }
407
+
408
+ #ifdef HAVE_SQLITE3_COLUMN_DATABASE_NAME
409
+
410
+ /* call-seq: stmt.database_name(column_index)
411
+ *
412
+ * Return the database name for the column at +column_index+
413
+ */
414
+ static VALUE database_name(VALUE self, VALUE index)
415
+ {
416
+ sqlite3StmtRubyPtr ctx;
417
+ Data_Get_Struct(self, sqlite3StmtRuby, ctx);
418
+ REQUIRE_OPEN_STMT(ctx);
419
+
420
+ return SQLITE3_UTF8_STR_NEW2(
421
+ sqlite3_column_database_name(ctx->st, NUM2INT(index)));
422
+ }
423
+
424
+ #endif
425
+
426
+ void init_sqlite3_statement()
427
+ {
428
+ cSqlite3Statement = rb_define_class_under(mSqlite3, "Statement", rb_cObject);
429
+
430
+ rb_define_alloc_func(cSqlite3Statement, allocate);
431
+ rb_define_method(cSqlite3Statement, "initialize", initialize, 2);
432
+ rb_define_method(cSqlite3Statement, "close", sqlite3_rb_close, 0);
433
+ rb_define_method(cSqlite3Statement, "closed?", closed_p, 0);
434
+ rb_define_method(cSqlite3Statement, "bind_param", bind_param, 2);
435
+ rb_define_method(cSqlite3Statement, "reset!", reset_bang, 0);
436
+ rb_define_method(cSqlite3Statement, "clear_bindings!", clear_bindings, 0);
437
+ rb_define_method(cSqlite3Statement, "step", step, 0);
438
+ rb_define_method(cSqlite3Statement, "done?", done_p, 0);
439
+ rb_define_method(cSqlite3Statement, "column_count", column_count, 0);
440
+ rb_define_method(cSqlite3Statement, "column_name", column_name, 1);
441
+ rb_define_method(cSqlite3Statement, "column_decltype", column_decltype, 1);
442
+ rb_define_method(cSqlite3Statement, "bind_parameter_count", bind_parameter_count, 0);
443
+
444
+ #ifdef HAVE_SQLITE3_COLUMN_DATABASE_NAME
445
+ rb_define_method(cSqlite3Statement, "database_name", database_name, 1);
446
+ #endif
447
+ }
@@ -0,0 +1,16 @@
1
+ #ifndef SQLITE3_STATEMENT_RUBY
2
+ #define SQLITE3_STATEMENT_RUBY
3
+
4
+ #include <sqlite3_ruby.h>
5
+
6
+ struct _sqlite3StmtRuby {
7
+ sqlite3_stmt *st;
8
+ int done_p;
9
+ };
10
+
11
+ typedef struct _sqlite3StmtRuby sqlite3StmtRuby;
12
+ typedef sqlite3StmtRuby * sqlite3StmtRubyPtr;
13
+
14
+ void init_sqlite3_statement();
15
+
16
+ #endif
data/faq/faq.rb ADDED
@@ -0,0 +1,145 @@
1
+ require 'yaml'
2
+ require 'redcloth'
3
+
4
+ def process_faq_list( faqs )
5
+ puts "<ul>"
6
+ faqs.each do |faq|
7
+ process_faq_list_item faq
8
+ end
9
+ puts "</ul>"
10
+ end
11
+
12
+ def process_faq_list_item( faq )
13
+ question = faq.keys.first
14
+ answer = faq.values.first
15
+
16
+ print "<li>"
17
+
18
+ question_text = RedCloth.new(question).to_html.gsub( %r{</?p>},"" )
19
+ if answer.is_a?( Array )
20
+ puts question_text
21
+ process_faq_list answer
22
+ else
23
+ print "<a href='##{question.object_id}'>#{question_text}</a>"
24
+ end
25
+
26
+ puts "</li>"
27
+ end
28
+
29
+ def process_faq_descriptions( faqs, path=nil )
30
+ faqs.each do |faq|
31
+ process_faq_description faq, path
32
+ end
33
+ end
34
+
35
+ def process_faq_description( faq, path )
36
+ question = faq.keys.first
37
+ path = ( path ? path + " " : "" ) + question
38
+ answer = faq.values.first
39
+
40
+ if answer.is_a?( Array )
41
+ process_faq_descriptions( answer, path )
42
+ else
43
+ title = RedCloth.new( path ).to_html.gsub( %r{</?p>}, "" )
44
+ answer = RedCloth.new( answer || "" )
45
+
46
+ puts "<a name='#{question.object_id}'></a>"
47
+ puts "<div class='faq-title'>#{title}</div>"
48
+ puts "<div class='faq-answer'>#{add_api_links(answer.to_html)}</div>"
49
+ end
50
+ end
51
+
52
+ API_OBJECTS = [ "Database", "Statement", "ResultSet",
53
+ "ParsedStatement", "Pragmas", "Translator" ].inject( "(" ) { |acc,name|
54
+ acc << "|" if acc.length > 1
55
+ acc << name
56
+ acc
57
+ } + ")"
58
+
59
+ def add_api_links( text )
60
+ text.gsub( /#{API_OBJECTS}(#(\w+))?/ ) do
61
+ disp_obj = obj = $1
62
+
63
+ case obj
64
+ when "Pragmas"; disp_obj = "Database"
65
+ end
66
+
67
+ method = $3
68
+ s = "<a href='http://sqlite-ruby.rubyforge.org/classes/SQLite/#{obj}.html'>#{disp_obj}"
69
+ s << "##{method}" if method
70
+ s << "</a>"
71
+ s
72
+ end
73
+ end
74
+
75
+ faqs = YAML.load( File.read( "faq.yml" ) )
76
+
77
+ puts <<-EOF
78
+ <html>
79
+ <head>
80
+ <title>SQLite3/Ruby FAQ</title>
81
+ <style type="text/css">
82
+ a, a:visited, a:active {
83
+ color: #00F;
84
+ text-decoration: none;
85
+ }
86
+
87
+ a:hover {
88
+ text-decoration: underline;
89
+ }
90
+
91
+ .faq-list {
92
+ color: #000;
93
+ font-family: vera-sans, verdana, arial, sans-serif;
94
+ }
95
+
96
+ .faq-title {
97
+ background: #007;
98
+ color: #FFF;
99
+ font-family: vera-sans, verdana, arial, sans-serif;
100
+ padding-left: 1em;
101
+ padding-top: 0.5em;
102
+ padding-bottom: 0.5em;
103
+ font-weight: bold;
104
+ font-size: large;
105
+ border: 1px solid #000;
106
+ }
107
+
108
+ .faq-answer {
109
+ margin-left: 1em;
110
+ color: #000;
111
+ font-family: vera-sans, verdana, arial, sans-serif;
112
+ }
113
+
114
+ .faq-answer pre {
115
+ margin-left: 1em;
116
+ color: #000;
117
+ background: #FFE;
118
+ font-size: normal;
119
+ border: 1px dotted #CCC;
120
+ padding: 1em;
121
+ }
122
+
123
+ h1 {
124
+ background: #005;
125
+ color: #FFF;
126
+ font-family: vera-sans, verdana, arial, sans-serif;
127
+ padding-left: 1em;
128
+ padding-top: 1em;
129
+ padding-bottom: 1em;
130
+ font-weight: bold;
131
+ font-size: x-large;
132
+ border: 1px solid #00F;
133
+ }
134
+ </style>
135
+ </head>
136
+ <body>
137
+ <h1>SQLite/Ruby FAQ</h1>
138
+ <div class="faq-list">
139
+ EOF
140
+
141
+ process_faq_list( faqs )
142
+ puts "</div>"
143
+ process_faq_descriptions( faqs )
144
+
145
+ puts "</body></html>"