hermeneutics 1.10 → 1.11

Sign up to get free protection for your applications and to get access to all the features.
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