code-ruby 3.1.2 → 4.0.1

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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/bin/code +100 -20
  4. data/lib/code/concerns/shared.rb +335 -15
  5. data/lib/code/format.rb +33 -15
  6. data/lib/code/network.rb +82 -0
  7. data/lib/code/node/call.rb +80 -2
  8. data/lib/code/node/call_argument.rb +14 -0
  9. data/lib/code/node/code.rb +4 -3
  10. data/lib/code/node/function_parameter.rb +7 -4
  11. data/lib/code/node/list.rb +32 -2
  12. data/lib/code/node/square_bracket.rb +4 -2
  13. data/lib/code/object/base_64.rb +132 -6
  14. data/lib/code/object/boolean.rb +56 -0
  15. data/lib/code/object/class.rb +143 -2
  16. data/lib/code/object/code.rb +108 -7
  17. data/lib/code/object/context.rb +59 -1
  18. data/lib/code/object/cryptography.rb +69 -0
  19. data/lib/code/object/date.rb +13800 -462
  20. data/lib/code/object/decimal.rb +1098 -0
  21. data/lib/code/object/dictionary.rb +1861 -11
  22. data/lib/code/object/duration.rb +24 -0
  23. data/lib/code/object/function.rb +289 -27
  24. data/lib/code/object/global.rb +447 -1
  25. data/lib/code/object/html.rb +181 -7
  26. data/lib/code/object/http.rb +253 -17
  27. data/lib/code/object/ics.rb +76 -13
  28. data/lib/code/object/identifier_list.rb +30 -10
  29. data/lib/code/object/integer.rb +1265 -2
  30. data/lib/code/object/json.rb +80 -1
  31. data/lib/code/object/list.rb +3371 -10
  32. data/lib/code/object/nothing.rb +53 -0
  33. data/lib/code/object/number.rb +120 -0
  34. data/lib/code/object/parameter.rb +149 -0
  35. data/lib/code/object/range.rb +530 -14
  36. data/lib/code/object/smtp.rb +103 -12
  37. data/lib/code/object/string.rb +968 -3
  38. data/lib/code/object/super.rb +11 -1
  39. data/lib/code/object/time.rb +13932 -498
  40. data/lib/code/object/url.rb +67 -0
  41. data/lib/code/object.rb +582 -0
  42. data/lib/code/parser.rb +194 -55
  43. data/lib/code-ruby.rb +3 -0
  44. data/lib/code.rb +30 -3
  45. metadata +135 -84
  46. data/.github/dependabot.yml +0 -15
  47. data/.github/workflows/ci.yml +0 -38
  48. data/.gitignore +0 -30
  49. data/.node-version +0 -1
  50. data/.npm-version +0 -1
  51. data/.prettierignore +0 -2
  52. data/.rspec +0 -1
  53. data/.rubocop.yml +0 -140
  54. data/.ruby-version +0 -1
  55. data/.tool-versions +0 -3
  56. data/AGENTS.md +0 -43
  57. data/Gemfile +0 -22
  58. data/Gemfile.lock +0 -292
  59. data/Rakefile +0 -5
  60. data/bin/bundle +0 -123
  61. data/bin/bundle-audit +0 -31
  62. data/bin/bundler-audit +0 -31
  63. data/bin/dorian +0 -31
  64. data/bin/rspec +0 -31
  65. data/bin/rubocop +0 -31
  66. data/bin/test +0 -5
  67. data/code-ruby.gemspec +0 -34
  68. data/docs/precedence.txt +0 -36
  69. data/package-lock.json +0 -14
  70. data/package.json +0 -7
  71. data/spec/bin/code_spec.rb +0 -48
  72. data/spec/code/format_spec.rb +0 -153
  73. data/spec/code/node/call_spec.rb +0 -11
  74. data/spec/code/object/boolean_spec.rb +0 -18
  75. data/spec/code/object/cryptography_spec.rb +0 -25
  76. data/spec/code/object/decimal_spec.rb +0 -50
  77. data/spec/code/object/dictionary_spec.rb +0 -98
  78. data/spec/code/object/function_spec.rb +0 -268
  79. data/spec/code/object/http_spec.rb +0 -33
  80. data/spec/code/object/ics_spec.rb +0 -50
  81. data/spec/code/object/integer_spec.rb +0 -42
  82. data/spec/code/object/list_spec.rb +0 -22
  83. data/spec/code/object/nothing_spec.rb +0 -14
  84. data/spec/code/object/range_spec.rb +0 -23
  85. data/spec/code/object/string_spec.rb +0 -26
  86. data/spec/code/parser/boolean_spec.rb +0 -11
  87. data/spec/code/parser/chained_call_spec.rb +0 -16
  88. data/spec/code/parser/dictionary_spec.rb +0 -18
  89. data/spec/code/parser/function_spec.rb +0 -16
  90. data/spec/code/parser/group_spec.rb +0 -11
  91. data/spec/code/parser/if_modifier_spec.rb +0 -18
  92. data/spec/code/parser/list_spec.rb +0 -17
  93. data/spec/code/parser/number_spec.rb +0 -11
  94. data/spec/code/parser/string_spec.rb +0 -20
  95. data/spec/code/parser_spec.rb +0 -52
  96. data/spec/code/type_spec.rb +0 -21
  97. data/spec/code_spec.rb +0 -717
  98. data/spec/spec_helper.rb +0 -21
  99. data/spec/zeitwerk/loader_spec.rb +0 -7
@@ -3,6 +3,34 @@
3
3
  class Code
4
4
  class Object
5
5
  class Smtp < Dictionary
6
+ CLASS_DOCUMENTATION = {
7
+ name: "Smtp",
8
+ description: "stores smtp settings and sends email messages.",
9
+ examples: %w[Smtp Smtp.new Smtp.new.functions.keys.include?(:send)]
10
+ }.freeze
11
+ INSTANCE_FUNCTIONS = {
12
+ "send" => {
13
+ name: "send",
14
+ description: "sends an email using the receiver's smtp settings.",
15
+ examples: %w[
16
+ Smtp.new.respond_to?(:send)
17
+ Smtp.new.functions.keys.include?(:send)
18
+ Smtp.new.instance_functions.keys.include?(:send)
19
+ ]
20
+ }
21
+ }.freeze
22
+
23
+ def self.function_documentation(scope)
24
+ return INSTANCE_FUNCTIONS if scope == :instance
25
+
26
+ {}
27
+ end
28
+
29
+ DEFAULT_PORT = 587
30
+ DEFAULT_TIMEOUT = 10
31
+ DEFAULT_DOMAIN = "localhost.localdomain"
32
+ ALLOWED_PORTS = [465, 587].freeze
33
+
6
34
  def call(**args)
7
35
  code_operator = args.fetch(:operator, nil).to_code
8
36
  code_arguments = args.fetch(:arguments, []).to_code
@@ -66,22 +94,85 @@ class Code
66
94
  mail.add_part(text_part)
67
95
  mail.add_part(html_part) if code_body_html.to_s.present?
68
96
 
69
- mail.delivery_method(
70
- :smtp,
71
- {
72
- address: code_get("address").to_s,
73
- port: code_get("port").to_i,
74
- user_name: code_get("user_name").to_s,
75
- password: code_get("password").to_s,
76
- authentication: code_get("authentication").to_s,
77
- enable_starttls_auto: code_get("enable_starttls_auto").truthy?
78
- }
79
- )
97
+ address = code_get("address").to_s
98
+ port = code_get("port").nothing? ? DEFAULT_PORT : code_get("port").to_i
99
+ authentication = code_get("authentication").to_s
100
+ starttls = code_get("enable_starttls_auto")
101
+ if starttls.something? && starttls.falsy?
102
+ raise Error, "smtp: tls is required"
103
+ end
104
+
105
+ resolved_ip = validate_delivery_target!(address, port)
80
106
 
81
- mail.deliver!
107
+ encoded_message = mail.encoded
108
+ ::Code.ensure_input_size!(encoded_message, label: "smtp message")
109
+
110
+ deliver_mail(
111
+ mail,
112
+ address: address,
113
+ resolved_ip: resolved_ip,
114
+ port: port,
115
+ user_name: code_get("user_name").to_s,
116
+ password: code_get("password").to_s,
117
+ authentication: authentication
118
+ )
82
119
 
83
120
  Nothing.new
84
121
  end
122
+
123
+ private
124
+
125
+ def validate_delivery_target!(address, port)
126
+ resolved_ip =
127
+ ::Code::Network.validate_public_host!(address, service: "smtp").first
128
+
129
+ return resolved_ip if ALLOWED_PORTS.include?(port)
130
+
131
+ raise Error, "smtp: unsupported port"
132
+ end
133
+
134
+ def deliver_mail(
135
+ mail,
136
+ address:,
137
+ resolved_ip:,
138
+ port:,
139
+ user_name:,
140
+ password:,
141
+ authentication:
142
+ )
143
+ envelope = Mail::SmtpEnvelope.new(mail)
144
+ smtp =
145
+ Net::SMTP.new(
146
+ resolved_ip,
147
+ port,
148
+ tls: port == 465,
149
+ starttls: port == 587 ? :always : false,
150
+ tls_verify: true,
151
+ tls_hostname: address,
152
+ ssl_context_params: {
153
+ verify_mode: OpenSSL::SSL::VERIFY_PEER
154
+ }
155
+ )
156
+ smtp.open_timeout = DEFAULT_TIMEOUT
157
+ smtp.read_timeout = DEFAULT_TIMEOUT
158
+
159
+ smtp.start(
160
+ DEFAULT_DOMAIN,
161
+ user_name.presence,
162
+ password.presence,
163
+ authentication.presence
164
+ ) do |connection|
165
+ connection.sendmail(envelope.message, envelope.from, envelope.to)
166
+ end
167
+ rescue Timeout::Error
168
+ raise Error, "smtp timeout"
169
+ rescue OpenSSL::SSL::SSLError,
170
+ IOError,
171
+ SystemCallError,
172
+ SocketError,
173
+ Net::SMTPError
174
+ raise Error, "smtp error"
175
+ end
85
176
  end
86
177
  end
87
178
  end