hermeneutics 1.10 → 1.11

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 721abdbe281ba643a27fefdcb8016c0f1a0202ed65e6c9741b5a90c3320c121e
4
- data.tar.gz: c78e80af37ced64e8ab89d19b729da96f3b6d0e888f0bf0c5de96274bbdf30c3
3
+ metadata.gz: f95dbd49c6d0f467ff381f459e23b2ede01e74a36cbffc71d7840efb1fd6e6c3
4
+ data.tar.gz: cafb0d288c12b99cfac80512d0da471124be8f8fb76f23f5f1c0b0ccaae602e1
5
5
  SHA512:
6
- metadata.gz: 30f6297db18bab61106428eee10338bfe4907dae30fa12bfbff8dc3e94a931b349befc4f5fa65bd21c9b053d9c9e8904d8c7d9920fc2f63f51d393167dc13a0b
7
- data.tar.gz: 9f96a1b60536b83254252029fa36fa3e7ed739f270799bd8ccd155e068407a4b31de0073bb7049c5306b7096f5c9b54dca7ba53f8564966f7e82e6830cc644f4
6
+ metadata.gz: 18e1e83b59d712df7c0d30adeb6d1c9d6cb80ed2d9ee5ebd3a6a458f1405a764c16ad85871bafaaac07752f4703f33853834df1e5257fe0bf569a8ad0c16fc5e
7
+ data.tar.gz: 5cf7d8ee879802e39c0483c871cab99609fb5b338f7598e9c4cf26762bc500dfceda23e68ba67848e47eecfb140b9a1d8fb3ffd3d1533d11747edfa72413f81a
data/bin/hermesmail CHANGED
@@ -164,10 +164,10 @@ module Hermeneutics
164
164
  LICENSE = Hermeneutics::LICENSE
165
165
  AUTHORS = Hermeneutics::AUTHORS
166
166
 
167
- DESCRIPTION = <<-EOT
168
- This mail delivery agent (MDA) reads a configuration file
169
- that is plain Ruby code. See the examples section for how
170
- to write one.
167
+ DESCRIPTION = <<~EOT
168
+ This mail delivery agent (MDA) reads a configuration file
169
+ that is plain Ruby code. See the examples section for how
170
+ to write one.
171
171
  EOT
172
172
 
173
173
  attr_accessor :rulesfile, :mbox, :fetchfile
@@ -2,6 +2,7 @@
2
2
  # hermeneutics/cgi.rb -- CGI responses
3
3
  #
4
4
 
5
+ require "supplement"
5
6
  require "hermeneutics/escape"
6
7
  require "hermeneutics/message"
7
8
  require "hermeneutics/html"
@@ -13,29 +14,54 @@ module Hermeneutics
13
14
 
14
15
  CONTENT_TYPE = "text/html"
15
16
 
16
- attr_reader :cgi
17
-
18
- def initialize cgi
19
- @cgi = cgi
20
- end
17
+ attr_accessor :cgi
21
18
 
22
19
  def form! **attrs, &block
23
- attrs[ :action] = @cgi.fullpath attrs[ :action]
20
+ attrs[ :action] = @cgi.fullname attrs[ :action]
24
21
  form **attrs, &block
25
22
  end
26
23
 
27
24
  def href dest, params = nil, anchor = nil
28
25
  @utx ||= URLText.new
29
- dest = @cgi.fullpath dest
26
+ dest = @cgi.fullname dest
30
27
  @utx.mkurl dest, params, anchor
31
28
  end
32
29
 
33
- def href! params = nil, anchor = nil
34
- href nil, params, anchor
30
+ def href! dest, params = nil, anchor = nil
31
+ dest = @cgi.fullpath dest
32
+ href dest, params, anchor
35
33
  end
36
34
 
37
35
  end
38
36
 
37
+ class Text
38
+ CONTENT_TYPE = "text/plain"
39
+ attr_accessor :cgi
40
+ def generate out = nil
41
+ @out = out||$stdout
42
+ yield
43
+ ensure
44
+ @out = nil
45
+ end
46
+ def document *args, **kwargs, &block
47
+ build *args, **kwargs, &block
48
+ end
49
+ def build *args, **kwargs, &block
50
+ end
51
+ private
52
+ def p *args
53
+ args.each { |a| @out << a.to_s }
54
+ end
55
+ def l arg
56
+ arg = arg.to_s
57
+ @out << arg
58
+ arg.ends_with? $/ or @out << $/
59
+ end
60
+ def nl
61
+ @out << $/
62
+ end
63
+ end
64
+
39
65
 
40
66
  # Example:
41
67
  #
@@ -65,104 +91,167 @@ module Hermeneutics
65
91
  end
66
92
  end
67
93
 
94
+ CGIENV = %w(content document gateway http query
95
+ remote request script server unique)
96
+
97
+ private
98
+
99
+ def method_missing sym, *args
100
+ if args.empty? and CGIENV.include? sym[ /\A(\w+?)_\w+\z/, 1] then
101
+ ENV[ sym.to_s.upcase]
102
+ else
103
+ super
104
+ end
105
+ end
106
+
107
+ public
108
+
109
+ def https?
110
+ ENV[ "HTTPS"].notempty?
111
+ end
112
+
68
113
  # Overwrite this.
114
+ #
115
+ # If you're reacting to POST uploads, please consider limiting
116
+ # the upload size.
117
+ # Apache: LimitRequestBody
118
+ # Nginx: client_max_body_size
119
+ # Lighttpd: server.max-request-size
120
+ #
69
121
  def run
70
122
  document Html
71
123
  end
72
124
 
73
- def parameters &block
125
+ def parameters! nl: false, sym: false, strip: false
126
+ @parameters ||= parameters nl: nl, sym: sym, strip: strip
127
+ nil
128
+ end
129
+
130
+ def parameters nl: false, sym: false, strip: false
74
131
  if block_given? then
75
- case request_method
76
- when "GET", "HEAD" then parse_query query_string, &block
77
- when "POST" then parse_posted &block
78
- else parse_input &block
132
+ data.parse do |k,v,**kw|
133
+ k = k.to_sym if sym
134
+ if v then
135
+ v.strip! if strip
136
+ v.gsub! "\r\n", "\n" if nl
137
+ end
138
+ yield k, v.notempty?, **kw
79
139
  end
80
140
  else
81
141
  p = {}
82
- parameters do |k,v|
142
+ parameters nl: nl, sym: sym, strip: strip do |k,v|
83
143
  p[ k] = v
84
144
  end
85
145
  p
86
146
  end
87
147
  end
88
148
 
89
- CGIENV = %w(content document gateway http query
90
- remote request script server unique)
91
-
92
- def method_missing sym, *args
93
- if args.empty? and CGIENV.include? sym[ /\A(\w+?)_\w+\z/, 1] then
94
- ENV[ sym.to_s.upcase]
95
- else
96
- super
149
+ def data
150
+ case request_method
151
+ when "GET", "HEAD" then
152
+ Data::UrlEnc.new query_string
153
+ when "POST" then
154
+ data = $stdin.read
155
+ data.bytesize == content_length.to_i or
156
+ warn "Content length #{content_length} is wrong (#{data.bytesize})."
157
+ ct = ContentType.parse content_type
158
+ data.force_encoding ct[ :charset]||Encoding::ASCII_8BIT
159
+ case ct.fulltype
160
+ when "application/x-www-form-urlencoded" then
161
+ Data::UrlEnc.new data
162
+ when "multipart/form-data" then
163
+ Data::Multiparted.new data, ct.hash
164
+ when "text/plain" then
165
+ Data::Plain.new data
166
+ when "application/json" then
167
+ Data::Json.new data
168
+ when "application/x-yaml", "application/yaml" then
169
+ Data::Yaml.new data
170
+ else
171
+ Data::UrlEnc.new data
172
+ end
173
+ else
174
+ Data::Lines.new read_interactive
97
175
  end
98
176
  end
99
177
 
100
- def https?
101
- ENV[ "HTTPS"].notempty?
102
- end
103
178
 
104
179
  private
105
180
 
106
- def parse_query data, &block
107
- URLText.decode_hash data, &block
108
- end
109
-
110
- def parse_posted &block
111
- data = $stdin.read.force_encoding Encoding::ASCII_8BIT
112
- data.bytesize == content_length.to_i or
113
- @warn = "Content length #{content_length} is wrong (#{data.bytesize})."
114
- ct = ContentType.parse content_type
115
- case ct.fulltype
116
- when "application/x-www-form-urlencoded" then
117
- parse_query data, &block
118
- when "multipart/form-data" then
119
- mp = Multipart.parse data, **ct.hash
120
- parse_multipart mp, &block
121
- when "text/plain" then
122
- # Suppose this is for testing purposes only.
123
- mk_params data.lines, &block
124
- else
125
- parse_query data, &block
181
+ module Data
182
+ class Plain
183
+ attr_reader :data
184
+ def initialize data
185
+ @data = data
186
+ end
126
187
  end
127
- end
128
-
129
- def parse_multipart mp
130
- mp.each { |part|
131
- cd = part.headers.content_disposition
132
- if cd.caption == "form-data" then
133
- yield cd.name, part.body_decoded, **cd.hash
188
+ class UrlEnc < Plain
189
+ def parse &block
190
+ URLText.decode_hash @data, &block
134
191
  end
135
- }
192
+ end
193
+ class Multiparted < Plain
194
+ def initialize data, params
195
+ super data
196
+ @params = params
197
+ end
198
+ def parse
199
+ mp = Multipart.parse @data, **@params
200
+ mp.each { |part|
201
+ cd = part.headers.content_disposition
202
+ if cd.caption == "form-data" then
203
+ yield cd.name, part.body_decoded, **cd.hash
204
+ end
205
+ }
206
+ end
207
+ end
208
+ class Lines < Plain
209
+ def initialize lines
210
+ @lines = lines
211
+ end
212
+ def data ; @lines.join $/ ; end
213
+ def parse
214
+ @lines.each { |s|
215
+ k, v = s.split %r/=/
216
+ v ||= k
217
+ [k, v].each { |x| x.strip! }
218
+ yield k, v
219
+ }
220
+ end
221
+ end
222
+ class Json < Plain
223
+ def parse &block
224
+ require "json"
225
+ (JSON.load @data).each_pair &block
226
+ end
227
+ end
228
+ class Yaml < Plain
229
+ def parse &block
230
+ require "yaml"
231
+ (YAML.load @data).each_pair &block
232
+ end
233
+ end
136
234
  end
137
235
 
138
- def mk_params l
139
- l.each { |s|
140
- k, v = s.split %r/=/
141
- v ||= k
142
- [k, v].each { |x| x.strip! }
143
- yield k, v
144
- }
145
- end
146
236
 
147
- def parse_input &block
237
+ def read_interactive
238
+ ENV[ "SCRIPT_NAME"] ||= $0
148
239
  if $*.any? then
149
- l = $*
240
+ $*
150
241
  else
151
242
  if $stdin.tty? then
152
- $stderr.puts <<~EOT
153
- Offline mode: Enter name=value pairs on standard input.
154
- EOT
155
- end
156
- l = []
157
- while (a = $stdin.gets) and a !~ /^$/ do
158
- l.push a
243
+ $stderr.puts "Offline mode: Enter name=value pairs on standard input."
244
+ l = []
245
+ while (a = $stdin.gets) and a !~ /^$/ do
246
+ l.push a
247
+ end
248
+ l
249
+ else
250
+ $stdin.read.split $/
159
251
  end
160
252
  end
161
- ENV[ "SCRIPT_NAME"] = $0
162
- mk_params l, &block
163
253
  end
164
254
 
165
-
166
255
  class Done < Exception
167
256
  attr_reader :result
168
257
  def initialize result
@@ -186,10 +275,14 @@ module Hermeneutics
186
275
  run
187
276
  rescue
188
277
  done { |res|
189
- res.body = "#$! (#{$!.class})#$/"
190
- $@.each { |a| res.body << "\t" << a << $/ }
191
- res.headers.add :content_type,
192
- "text/plain", charset: res.body.encoding
278
+ res.body = if $!.class.const_defined? :HTTP_STATUS then
279
+ res.headers.add :status, "%03d" % $!.class::HTTP_STATUS
280
+ $!.message + $/
281
+ else
282
+ # Why doesn't Ruby provide the encoding of #message?
283
+ ($!.full_message highlight: false, order: :top).force_encoding $!.message.encoding
284
+ end
285
+ res.headers.add :content_type, "text/plain", charset: res.body.encoding
193
286
  }
194
287
  end
195
288
  rescue Done
@@ -200,7 +293,8 @@ module Hermeneutics
200
293
 
201
294
  def document cls = Html, *args, &block
202
295
  done { |res|
203
- doc = cls.new self
296
+ doc = cls.new
297
+ doc.cgi = self
204
298
  res.body = ""
205
299
  doc.generate res.body do
206
300
  doc.document *args, &block
@@ -209,8 +303,14 @@ module Hermeneutics
209
303
  ct = if doc.respond_to? :content_type then doc.content_type
210
304
  elsif cls.const_defined? :CONTENT_TYPE then doc.class::CONTENT_TYPE
211
305
  end
212
- ct and res.headers.add :content_type, ct,
213
- charset: res.body.encoding||Encoding.default_external
306
+ if ct then
307
+ cs = if doc.respond_to? :charset then doc.charset
308
+ elsif cls.const_defined? :CHARSET then doc.class::CHARSET
309
+ else
310
+ res.body.encoding||Encoding.default_external
311
+ end
312
+ res.headers.add :content_type, ct, charset: cs
313
+ end
214
314
  if doc.respond_to? :cookies then
215
315
  doc.cookies do |c|
216
316
  res.headers.add :set_cookie, c
@@ -225,20 +325,35 @@ module Hermeneutics
225
325
  end
226
326
  utx = URLText.new mask_space: true
227
327
  unless dest =~ %r{\A\w+://} then
228
- dest = %Q'#{https? ? "https" : "http"}://#{http_host}#{fullpath dest}'
328
+ dest = %Q'#{https? ? "https" : "http"}://#{http_host||"localhost"}#{fullpath dest}'
229
329
  end
230
330
  url = utx.mkurl dest, params, anchor
231
331
  done { |res| res.headers.add "Location", url }
232
332
  end
233
333
 
234
- def fullpath dest
334
+ def fullname dest
235
335
  if dest then
236
- dest =~ %r{\A/} || dest =~ /\.\w+\z/ ? dest : dest + ".rb"
336
+ if dest =~ /\.\w+\z/ then
337
+ dest
338
+ else
339
+ "#{dest}.rb"
340
+ end
237
341
  else
238
- File.basename script_name
342
+ script_name
343
+ end
344
+ end
345
+
346
+ def fullpath dest
347
+ dest = fullname dest
348
+ unless File.absolute_path? dest then
349
+ dir = File.dirname script_name rescue ""
350
+ dest = File.join dir, dest
239
351
  end
240
352
  end
241
353
 
354
+ def warn msg
355
+ end
356
+
242
357
 
243
358
  if defined? MOD_RUBY then
244
359
  # This has not been tested.
@@ -247,13 +247,20 @@ module Hermeneutics
247
247
  end
248
248
 
249
249
 
250
- class Float
250
+ class Integer
251
251
  def to_gray
252
252
  Hermeneutics::Color.gray self
253
253
  end
254
254
  alias to_grey to_gray
255
255
  end
256
256
 
257
+ class Float
258
+ def to_gray
259
+ (0xff * self).to_i.to_gray
260
+ end
261
+ alias to_grey to_gray
262
+ end
263
+
257
264
  class String
258
265
  def to_gray
259
266
  (Integer self).to_gray
@@ -123,6 +123,8 @@ module Hermeneutics
123
123
  def [] key ; @hash[ key.to_sym] ; end
124
124
  alias field []
125
125
 
126
+ private
127
+
126
128
  def method_missing sym, *args
127
129
  if sym =~ /[^a-z_]/ or args.any? then
128
130
  super
@@ -131,6 +133,8 @@ module Hermeneutics
131
133
  end
132
134
  end
133
135
 
136
+ public
137
+
134
138
  # :call-seq:
135
139
  # keys() -> ary
136
140
  #
@@ -182,6 +182,8 @@ module Hermeneutics
182
182
  protected_instance_methods + instance_methods)
183
183
  undef_method *m
184
184
 
185
+ private
186
+
185
187
  def method_missing sym, *args, &block
186
188
  if Html::TAGS[ sym] then
187
189
  if args.any? and not Hash === args.first then
@@ -198,6 +200,8 @@ module Hermeneutics
198
200
  end
199
201
  end
200
202
 
203
+ public
204
+
201
205
  def properties *args
202
206
  write @selector.to_s, *args
203
207
  end
@@ -347,6 +347,7 @@ module Hermeneutics
347
347
  else val.to_s.notempty?
348
348
  end
349
349
  end
350
+ private
350
351
  def method_missing sym, *args
351
352
  if args.empty? and not sym =~ /[!?=]\z/ then
352
353
  self[ sym]
@@ -121,6 +121,10 @@ module Hermeneutics
121
121
  @out.path
122
122
  rescue NoMethodError
123
123
  end
124
+ def merge str
125
+ do_ind
126
+ @out << str
127
+ end
124
128
  def plain str
125
129
  do_ind
126
130
  @out << (@ent.encode str)
@@ -319,6 +323,8 @@ module Hermeneutics
319
323
  TAGS[ name]
320
324
  end
321
325
 
326
+ private
327
+
322
328
  def method_missing name, *args, &block
323
329
  t = tag? name
324
330
  t or super
@@ -330,10 +336,16 @@ module Hermeneutics
330
336
  end
331
337
  end
332
338
 
339
+ public
340
+
341
+ def merge str
342
+ @generator.merge str
343
+ nil
344
+ end
345
+
333
346
  def pcdata *strs
334
347
  strs.each { |s|
335
- s or next
336
- @generator.plain s
348
+ @generator.plain s if s.notempty?
337
349
  }
338
350
  nil
339
351
  end
@@ -343,9 +355,13 @@ module Hermeneutics
343
355
  self
344
356
  end
345
357
 
346
- def _ str = nil
347
- @generator.plain str||yield
348
- nil
358
+ def _ *strs
359
+ if strs.notempty? then
360
+ pcdata *strs
361
+ else
362
+ @generator.plain yield
363
+ nil
364
+ end
349
365
  end
350
366
 
351
367
  def comment str
@@ -362,64 +378,26 @@ module Hermeneutics
362
378
  method_missing :html, **attrs do yield end
363
379
  end
364
380
 
365
- def head attrs = nil
366
- method_missing :head, attrs do
381
+ def head **attrs
382
+ method_missing :head, **attrs do
367
383
  meta charset: @generator.encoding
368
384
  yield
369
385
  end
370
386
  end
371
387
 
372
- def form attrs, &block
373
- attrs[ :method] ||= if attrs[ :enctype] == "multipart/form-data" then
374
- "post"
375
- else
376
- "get"
377
- end
378
- @tabindex = 0
379
- method_missing :form, attrs, &block
380
- ensure
381
- @tabindex = nil
388
+ def h i, **attrs, &block
389
+ method_missing :"h#{i.to_i}", **attrs, &block
382
390
  end
383
391
 
384
- Field = Struct[ :type, :attrs]
385
-
386
- def field type, attrs
387
- attrs[ :id] ||= attrs[ :name]
388
- @tabindex += 1
389
- attrs[ :tabindex] ||= @tabindex
390
- Field[ type, attrs]
392
+ def form **attrs, &block
393
+ attrs[ :method] ||=
394
+ attrs[ :enctype] == "multipart/form-data" ? "post" : "get"
395
+ method_missing :form, **attrs, &block
391
396
  end
392
397
 
393
- def label field = nil, attrs = nil, &block
394
- if String === attrs then
395
- label field do attrs end
396
- else
397
- if Field === field or attrs then
398
- attrs = attrs.merge for: field.attrs[ :id]
399
- else
400
- attrs = field
401
- end
402
- method_missing :label, attrs, &block
403
- end
404
- end
405
-
406
- def input arg, &block
407
- if Field === arg then
408
- case arg.type
409
- when /select/i then
410
- method_missing :select, arg.attrs, &block
411
- when /textarea/i then
412
- block and
413
- raise ArgumentError, "Field textarea: use the value attribute."
414
- v = arg.attrs.delete :value
415
- method_missing :textarea, arg.attrs do v end
416
- else
417
- arg.attrs[ :type] ||= arg.type
418
- method_missing :input, arg.attrs, &block
419
- end
420
- else
421
- method_missing :input, arg, &block
422
- end
398
+ def input **attrs, &block
399
+ attrs[ :id] ||= attrs[ :name]
400
+ method_missing :input, **attrs, &block
423
401
  end
424
402
 
425
403
  end
@@ -433,8 +411,8 @@ module Hermeneutics
433
411
  super
434
412
  end
435
413
 
436
- def a attrs = nil
437
- attrs[ :name] ||= attrs[ :id] if attrs
414
+ def a **attrs
415
+ attrs[ :name] ||= attrs[ :id]
438
416
  super
439
417
  end
440
418
 
@@ -413,6 +413,8 @@ module Hermeneutics
413
413
  end
414
414
  end
415
415
 
416
+ private
417
+
416
418
  def method_missing sym, *args
417
419
  if args.empty? and not sym =~ /[!?=]\z/ then
418
420
  field sym, *args
@@ -421,6 +423,8 @@ module Hermeneutics
421
423
  end
422
424
  end
423
425
 
426
+ public
427
+
424
428
  def add name, *contents
425
429
  e = build_entry name, *contents
426
430
  add_entry e
@@ -544,6 +548,8 @@ module Hermeneutics
544
548
  @headers ||= Headers.create
545
549
  end
546
550
 
551
+ private
552
+
547
553
  def method_missing sym, *args, &block
548
554
  case sym
549
555
  when /h_(.*)/, /header_(.*)/ then
@@ -553,6 +559,8 @@ module Hermeneutics
553
559
  end
554
560
  end
555
561
 
562
+ public
563
+
556
564
  def [] name, type = nil
557
565
  @headers[ name, type]
558
566
  end
@@ -288,12 +288,16 @@ module Hermeneutics
288
288
  nil
289
289
  end
290
290
 
291
+ private
292
+
291
293
  def method_missing sym, *args
292
294
  (tag sym, *args) or super
293
295
  rescue
294
296
  super
295
297
  end
296
298
 
299
+ public
300
+
297
301
  def data
298
302
  d = ""
299
303
  gather_data self, d
@@ -14,7 +14,7 @@
14
14
  module Hermeneutics
15
15
 
16
16
  NAME = "hermeneutics"
17
- VERSION = "1.10".freeze
17
+ VERSION = "1.11".freeze
18
18
  SUMMARY = "CGI and mail handling"
19
19
 
20
20
  DESCRIPTION = <<~EOT
@@ -22,7 +22,7 @@ module Hermeneutics
22
22
  and CSS. Further, it is a CGI library.
23
23
  EOT
24
24
 
25
- COPYRIGHT = "(C) 2013-2020 Bertram Scharpf"
25
+ COPYRIGHT = "(C) 2013-2021 Bertram Scharpf"
26
26
  LICENSE = "BSD-2-Clause"
27
27
  AUTHORS = [ "Bertram Scharpf"]
28
28
  MAIL = "<software@bertram-scharpf.de>"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hermeneutics
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.10'
4
+ version: '1.11'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bertram Scharpf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-25 00:00:00.000000000 Z
11
+ date: 2021-06-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: supplement
@@ -76,7 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
76
  version: '0'
77
77
  requirements:
78
78
  - Just Ruby
79
- rubygems_version: 3.0.6
79
+ rubygems_version: 3.0.8
80
80
  signing_key:
81
81
  specification_version: 4
82
82
  summary: CGI and mail handling