code-ruby 5.1.0 → 5.2.2

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: eec903cbdd5d6368b89062c2b714515f30296bc14da6c56955cfb56883d1c8ec
4
- data.tar.gz: efff6f91bce16b5bd93295f694be06337a7edbbfd3c882108ffc0f4b1381be17
3
+ metadata.gz: 52e251b96064139d4e72a393de2b727b5d792ff0026d3d5f4830e582b54a7573
4
+ data.tar.gz: 456d6ddc2f99e1d662273af433beaf0461e960be102d829c26a9637af0c29bae
5
5
  SHA512:
6
- metadata.gz: aaa741df2a2cedebe5db8b763cb0aaac30f5829175baa76a90b4207bbfdb31f2b3eb87cf397eb475ef50e881db24c054e199677f09928f56c359712d5ee83101
7
- data.tar.gz: 59c8234dfb28efcb3672fbfb23673bfa5f3748edb92618fc0b5bda474ad700cca898da79f705ce750ac22b47539605544bb5c15d9e53287d872f97e77ffd5d32
6
+ metadata.gz: bd013119d2df47feb3c438cee2dccb21e13a01ca319085f7a4dc012966efc02a21578b07ccd104e851f5a0d3c633ac45e07ffea4e7dc4ba34e4fd012fb9bbca8
7
+ data.tar.gz: 057ad7fc9b03bbe40e4c921833e0da2926c0dfb9688b5c63a389cb4e66a687354bebc9591fb95eb8f56f41785fac888d976964c950c88c5532646c164e35c9ed
data/VERSION CHANGED
@@ -1 +1 @@
1
- 5.1.0
1
+ 5.2.2
data/lib/code/format.rb CHANGED
@@ -459,11 +459,12 @@ class Code
459
459
  extract_string_concatenation_parts(left_operation: operation)
460
460
  return format_string(merged_string, indent: indent) if merged_string
461
461
 
462
- expression = format_nested_statement(operation[:first], indent: indent)
462
+ first_operator = format_operator(Array(operation[:others]).first&.fetch(:operator, ""))
463
+ expression = format_boolean_operand(operation[:first], first_operator, indent: indent)
463
464
 
464
465
  Array(operation[:others]).each do |other|
465
- right = format_nested_statement(other[:statement], indent: indent)
466
466
  operator = format_operator(other[:operator])
467
+ right = format_boolean_operand(other[:statement], operator, indent: indent)
467
468
 
468
469
  expression =
469
470
  if compact_operator?(operator)
@@ -510,6 +511,21 @@ class Code
510
511
  expression
511
512
  end
512
513
 
514
+ def format_boolean_operand(statement, operator, indent:)
515
+ if BOOLEAN_WORD_OPERATORS.include?(operator) && statement.is_a?(Hash) &&
516
+ statement[:left_operation]
517
+ nested = Array(statement[:left_operation][:others])
518
+ if nested.any? do |other|
519
+ nested_operator = format_operator(other[:operator])
520
+ BOOLEAN_WORD_OPERATORS.include?(nested_operator) && nested_operator != operator
521
+ end
522
+ return format_group([statement], indent: indent)
523
+ end
524
+ end
525
+
526
+ format_nested_statement(statement, indent: indent)
527
+ end
528
+
513
529
  def format_compact_receiver(expression, indent:)
514
530
  return expression unless compact_receiver_needs_parentheses?(expression)
515
531
 
@@ -13,7 +13,18 @@ class Code
13
13
  "Cryptography.sha512(:hello, format: :base64)"
14
14
  ]
15
15
  }.freeze
16
+ HMAC_ALGORITHM = "SHA256"
17
+ DEFAULT_FORMAT = "hex"
16
18
  CLASS_FUNCTIONS = {
19
+ "hmac_sha256" => {
20
+ name: "hmac_sha256",
21
+ description: "returns an HMAC SHA256 signature as hex, digest, or base64.",
22
+ examples: [
23
+ 'Cryptography.hmac_sha256("key", "hello")',
24
+ 'Cryptography.hmac_sha256("key", "hello", format: :base64)',
25
+ 'Cryptography.hmac_sha256("key", "hello", format: :digest).size'
26
+ ]
27
+ },
17
28
  "md5" => {
18
29
  name: "md5",
19
30
  description:
@@ -78,6 +89,9 @@ class Code
78
89
  arguments = code_arguments.raw
79
90
 
80
91
  case code_operator.to_s
92
+ when "hmac_sha256"
93
+ sig(args) { [Object, Object, { format: String.maybe }] }
94
+ code_hmac_sha256(*arguments)
81
95
  when "md5"
82
96
  sig(args) { [Object, { format: String.maybe }] }
83
97
  code_md5(*arguments)
@@ -98,6 +112,22 @@ class Code
98
112
  end
99
113
  end
100
114
 
115
+ def self.code_hmac_sha256(key, payload, options = nil)
116
+ code_format = (options || {}).to_code.code_get("format")
117
+ format = code_format.nothing? ? DEFAULT_FORMAT : code_format.to_s.downcase
118
+ digest = OpenSSL::HMAC.digest(HMAC_ALGORITHM, key.to_s, payload.to_s)
119
+ case format
120
+ when "hex", "hexdigest"
121
+ digest.unpack1("H*").to_code
122
+ when "digest"
123
+ digest.to_code
124
+ when "base64"
125
+ ::Base64.strict_encode64(digest).to_code
126
+ else
127
+ raise Error, "Cryptography: unknown format #{format.inspect}"
128
+ end
129
+ end
130
+
101
131
  def self.code_md5(*arguments)
102
132
  payload = arguments[0]
103
133
  options = arguments[1] || Nothing.new
@@ -197,7 +197,8 @@ class Code
197
197
  not_extended: 510,
198
198
  network_authentication_required: 511
199
199
  }.freeze
200
- DEFAULT_TIMEOUT = 1.hour.to_f
200
+ DEFAULT_TIMEOUT = 30
201
+ DEFAULT_OPEN_TIMEOUT = 10
201
202
  MAX_HEADER_BYTES = 32.kilobytes
202
203
  HEADER_NAME = /\A[A-Za-z0-9!#$%&'*+\-.^_`|~]+\z/
203
204
  RESTRICTED_HEADERS = %w[
@@ -348,7 +349,7 @@ class Code
348
349
  redirects =
349
350
  (
350
351
  if options.code_get("redirects").nothing?
351
- 10
352
+ 0
352
353
  else
353
354
  options.code_get("redirects").raw
354
355
  end
@@ -410,7 +411,11 @@ class Code
410
411
  http.use_ssl = true if uri.scheme == "https"
411
412
  http.max_retries = max_retries
412
413
  default_timeout = http_timeout(timeout, DEFAULT_TIMEOUT)
413
- open_timeout_value = http_timeout(open_timeout, default_timeout)
414
+ open_timeout_value =
415
+ http_timeout(
416
+ open_timeout,
417
+ timeout.nothing? ? DEFAULT_OPEN_TIMEOUT : default_timeout
418
+ )
414
419
  read_timeout_value = http_timeout(read_timeout, default_timeout)
415
420
  write_timeout_value = http_timeout(write_timeout, default_timeout)
416
421
 
@@ -8,6 +8,38 @@ class Code
8
8
  description: "stores smtp settings and sends email messages.",
9
9
  examples: %w[Smtp Smtp.new Smtp.new.functions.keys.include?(:send)]
10
10
  }.freeze
11
+ MESSAGE_ARGUMENTS = {
12
+ from: String.maybe,
13
+ to: String.maybe,
14
+ subject: String.maybe,
15
+ body: String.maybe,
16
+ body_text: String.maybe,
17
+ body_html: String.maybe,
18
+ message_id: String.maybe
19
+ }.freeze
20
+ TEXT_CONTENT_TYPE = "text/plain; charset=UTF-8"
21
+ HTML_CONTENT_TYPE = "text/html; charset=UTF-8"
22
+ MULTIPART_CONTENT_TYPE = "multipart/alternative"
23
+ CLASS_FUNCTIONS = {
24
+ "encode" => {
25
+ name: "encode",
26
+ description: "encodes a message as MIME email.",
27
+ examples: [
28
+ 'Smtp.encode({from: "a@example.com", to: "b@example.com", body: "hello"})',
29
+ 'Smtp.encode({subject: "hello", body_text: "world"})',
30
+ 'Smtp.encode({body_html: "<p>hello</p>"})'
31
+ ]
32
+ },
33
+ "address" => {
34
+ name: "address",
35
+ description: "parses a single email address and display name.",
36
+ examples: [
37
+ 'Smtp.address("a@example.com")',
38
+ 'Smtp.address("Alice <a@example.com>")',
39
+ 'Smtp.address("Bob <b@example.com>")'
40
+ ]
41
+ }
42
+ }.freeze
11
43
  INSTANCE_FUNCTIONS = {
12
44
  "send" => {
13
45
  name: "send",
@@ -23,7 +55,7 @@ class Code
23
55
  def self.function_documentation(scope)
24
56
  return INSTANCE_FUNCTIONS if scope == :instance
25
57
 
26
- {}
58
+ CLASS_FUNCTIONS
27
59
  end
28
60
 
29
61
  DEFAULT_PORT = 587
@@ -31,6 +63,69 @@ class Code
31
63
  DEFAULT_DOMAIN = "localhost.localdomain"
32
64
  ALLOWED_PORTS = [465, 587].freeze
33
65
 
66
+ def self.call(**args)
67
+ code_operator = args.fetch(:operator, nil).to_code
68
+ code_arguments = args.fetch(:arguments, []).to_code
69
+
70
+ case code_operator.to_s
71
+ when "encode"
72
+ sig(args) { MESSAGE_ARGUMENTS }
73
+ code_encode(code_arguments.code_first)
74
+ when "address"
75
+ sig(args) { String }
76
+ code_address(*code_arguments.raw)
77
+ else
78
+ super
79
+ end
80
+ end
81
+
82
+ def self.code_address(value)
83
+ text = value.to_s
84
+ raise Error, "smtp: invalid address" if text.match?(/[\r\n]/)
85
+
86
+ addresses = Mail::AddressList.new(text).addresses
87
+ raise Error, "smtp: expected one address" unless addresses.length == 1
88
+
89
+ address = addresses.first
90
+ unless address.local.present? && address.domain.present?
91
+ raise Error, "smtp: invalid address"
92
+ end
93
+
94
+ { email: address.address, name: address.display_name }.to_code
95
+ rescue Mail::Field::ParseError
96
+ raise Error, "smtp: invalid address"
97
+ end
98
+
99
+ def self.code_encode(message)
100
+ build_mail(message.to_code).encoded.to_code
101
+ end
102
+
103
+ def self.build_mail(message)
104
+ mail = Mail.new
105
+ mail.from = message.code_get("from").to_s
106
+ mail.to = message.code_get("to").to_s
107
+ mail.subject = message.code_get("subject").to_s
108
+ message_id = message.code_get("message_id").to_s
109
+ if message_id.match?(/[\r\n]/)
110
+ raise Error, "smtp: invalid message id"
111
+ end
112
+
113
+ mail.message_id = message_id if message_id.present?
114
+
115
+ text_part = Mail::Part.new
116
+ text_part.content_type = TEXT_CONTENT_TYPE
117
+ text_part.body = message.code_get("body_text").to_s.presence || message.code_get("body").to_s
118
+ mail.content_type = MULTIPART_CONTENT_TYPE
119
+ mail.add_part(text_part)
120
+ if message.code_get("body_html").to_s.present?
121
+ html_part = Mail::Part.new
122
+ html_part.content_type = HTML_CONTENT_TYPE
123
+ html_part.body = message.code_get("body_html").to_s
124
+ mail.add_part(html_part)
125
+ end
126
+ mail
127
+ end
128
+
34
129
  def call(**args)
35
130
  code_operator = args.fetch(:operator, nil).to_code
36
131
  code_arguments = args.fetch(:arguments, []).to_code
@@ -38,16 +133,7 @@ class Code
38
133
 
39
134
  case code_operator.to_s
40
135
  when "send"
41
- sig(args) do
42
- {
43
- from: String.maybe,
44
- to: String.maybe,
45
- subject: String.maybe,
46
- body: String.maybe,
47
- body_text: String.maybe,
48
- body_html: String.maybe
49
- }
50
- end
136
+ sig(args) { MESSAGE_ARGUMENTS }
51
137
 
52
138
  code_send(
53
139
  from: code_value.code_get("from"),
@@ -55,7 +141,8 @@ class Code
55
141
  subject: code_value.code_get("subject"),
56
142
  body: code_value.code_get("body"),
57
143
  body_text: code_value.code_get("body_text"),
58
- body_html: code_value.code_get("body_html")
144
+ body_html: code_value.code_get("body_html"),
145
+ message_id: code_value.code_get("message_id")
59
146
  )
60
147
  else
61
148
  super
@@ -68,31 +155,12 @@ class Code
68
155
  subject: nil,
69
156
  body: nil,
70
157
  body_text: nil,
71
- body_html: nil
158
+ body_html: nil,
159
+ message_id: nil
72
160
  )
73
- code_from = from.to_code
74
- code_to = to.to_code
75
- code_subject = subject.to_code
76
- code_body = body.to_code
77
- code_body_text = body_text.to_code
78
- code_body_html = body_html.to_code
79
-
80
- mail = Mail.new
81
- mail.from = code_from.to_s
82
- mail.to = code_to.to_s
83
- mail.subject = code_subject.to_s
84
-
85
- text_part = Mail::Part.new
86
- text_part.content_type = "text/plain; charset=UTF-8"
87
- text_part.body = code_body_text.to_s.presence || code_body.to_s
88
-
89
- html_part = Mail::Part.new
90
- html_part.content_type = "text/html; charset=UTF-8"
91
- html_part.body = code_body_html.to_s
92
-
93
- mail.content_type = "multipart/alternative"
94
- mail.add_part(text_part)
95
- mail.add_part(html_part) if code_body_html.to_s.present?
161
+ mail = self.class.build_mail(
162
+ { from:, to:, subject:, body:, body_text:, body_html:, message_id: }.to_code
163
+ )
96
164
 
97
165
  address = code_get("address").to_s
98
166
  port = code_get("port").nothing? ? DEFAULT_PORT : code_get("port").to_i
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: code-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0
4
+ version: 5.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dorian Marié