dhall 0.1.0 → 0.2.0

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
  SHA1:
3
- metadata.gz: aaa68fc11b3223d666fd199cf507d4ca5744e31d
4
- data.tar.gz: 0b3643c8d75220906f9ac34b08e47c613bb1efb5
3
+ metadata.gz: 2189303de0238086f008d9fa07f0a24de1f0d825
4
+ data.tar.gz: 33dd23d2bcffb5e1171a7a0a08320c02be502fe1
5
5
  SHA512:
6
- metadata.gz: c5225d59efa8144191b163ec0cc565ec78846d7107f479e4dccc78056ff38a744865e86c857cfedf0808f836c292044bed91aaa8c4b74b844b9f380fc14da366
7
- data.tar.gz: 675ae2377aabb52b68a6f5db83eb8ebab10b9a44f13d5025908b1d6adc2f8a20b25ea4e74e7b48adaf41ae982e23bc5f3e515c8572667717fa7ed21bf4666569
6
+ metadata.gz: 3e3385fa51023333875319b5355a8eb11dcb9e69b0bbaa6b2bbf368003ea46a79daf7819dfcc9e8e444f028e6edbbf33dba8a3e68043a2a92a97767f82819bca
7
+ data.tar.gz: d690a7f50b99ee3d8f7a16ab4e7439c42b1500cc23186e30a6db73c8c76af3ca452c9febc303f7ce179166cc55629188720a1ea9839469c3c29356559a25d2d6
data/README.md CHANGED
@@ -6,7 +6,7 @@ This is a Ruby implementation of the Dhall configuration language. Dhall is a p
6
6
 
7
7
  This project follows semantic versioning, and every tagged version claims to adhere to the version of the dhall-lang standard that is linked in the dhall-lang submodule.
8
8
 
9
- For the purposes of considering what is a "breaking change" only the API as documented in this documentation is considered, regardless of any other exposed parts of the library. Anything not documented here may change at any time, but backward-incompatible changes to anything documented here will be accompanied by a major-version increment.
9
+ For the purposes of considering what is a "breaking change" only the API as documented in this README is considered, regardless of any other exposed parts of the library. Anything not documented here may change at any time, but backward-incompatible changes to anything documented here will be accompanied by a major-version increment.
10
10
 
11
11
  ## Installation
12
12
 
@@ -45,7 +45,15 @@ Wherever possible, you should use the `Promise` API and treat `Dhall.load` as an
45
45
 
46
46
  Dhall.load("1 + 1").sync # => #<Dhall::Natural value=2>
47
47
 
48
- **This will block the thread it is run from until the whole load operation is complete. Never call #sync from an async context.**
48
+ **This will block the thread it is run from until the whole load operation is complete. Never call `#sync` from an async context.**
49
+
50
+ ### Timeout
51
+
52
+ It is possible for malicious entities to craft Dhall expressions which take an unreasonable amount of time to load. To protect against this, `Dhall.load` implements a timeout mechanism with a default of 10 seconds. You may specify an alternate timeout like so:
53
+
54
+ Dhall.load("1 + 1", timeout: 1) # 1 second timeout
55
+ Dhall.load("1 + 1", timeout: 0.1) # 0.1 second timeut
56
+ Dhall.load("1 + 1", timeout: Float::INFINITY) # Never timeout
49
57
 
50
58
  ### Customizing Import Resolution
51
59
 
@@ -174,7 +182,7 @@ A Dhall expression may be a list of other expressions. Lists are `Enumerable` a
174
182
  list[100] # => #<Dhall::OptionalNone value_type=...>
175
183
  list.reverse # => #<Dhall::List elements=[#<Dhall::Natural value=2>, #<Dhall::Natural value=1>] element_type=...>
176
184
  list.join(",") # => "1,2"
177
- list.to_a # => [1,2]
185
+ list.to_a # => [#<Dhall::Natural value=1>, #<Dhall::Natural value=2>]
178
186
  end
179
187
 
180
188
  ## Record
@@ -241,6 +249,25 @@ You may wish to convert your existing Ruby objects to Dhall expressions. This c
241
249
 
242
250
  Many methods on Dhall expressions call `#as_dhall` on their arguments, so you can define it on your own objects to produce a custom serialization.
243
251
 
252
+ If your object is already set up to customise its YAML serialization using `#encode_with`, the default `#as_dhall` implementation will use that.
253
+
254
+ When you want a full replacement for `YAML.safe_load` you can use the `Dhall::Coder` API:
255
+
256
+ Dhall::Coder.dump(1) # => "\x82\x0F\x01"
257
+ Dhall::Coder.load("\x82\x0F\x01") # => 1
258
+ Dhall::Coder.dump(Object.new) # => ArgumentError
259
+
260
+ coder = Dhall::Coder.new(safe: Object)
261
+ coder.load_async(coder.dump(Object.new)).then do |value|
262
+ value # => #<Object:0x...>
263
+ end
264
+
265
+ **Warning: calling `Dhall::Coder.load` or `Dhall::Coder#load` on an expression with imports will perform synchronous IO. See the warnings for the `#sync` method above.**
266
+
267
+ Both `Dhall::Coder` and all instances of `Dhall::Coder` are compatible to drop-in for `ActiveRecord::Base#serialize` like so in your models:
268
+
269
+ serialize :column, Dhall::Coder
270
+
244
271
  ## Porting from YAML or JSON Configuration
245
272
 
246
273
  To aid in converting your existing configurations or serialized data, there are included some experimental scripts:
data/bin/yaml-to-dhall CHANGED
@@ -5,4 +5,4 @@ require "dhall"
5
5
  require "yaml"
6
6
  using Dhall::AsDhall
7
7
 
8
- STDOUT.write(CBOR.encode(YAML.safe_load(STDIN.read).as_dhall))
8
+ STDOUT.write(CBOR.encode(YAML.safe_load(STDIN.read, [Symbol]).as_dhall))
data/dhall.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "dhall"
5
- spec.version = "0.1.0"
5
+ spec.version = `git describe --always --dirty`
6
6
  spec.authors = ["Stephen Paul Weber"]
7
7
  spec.email = ["dev@singpolyma.net"]
8
8
  spec.license = "GPL-3.0"
data/lib/dhall.rb CHANGED
@@ -4,6 +4,7 @@ require "dhall/as_dhall"
4
4
  require "dhall/ast"
5
5
  require "dhall/binary"
6
6
  require "dhall/builtins"
7
+ require "dhall/coder"
7
8
  require "dhall/normalize"
8
9
  require "dhall/parser"
9
10
  require "dhall/resolve"
@@ -12,26 +13,38 @@ require "dhall/typecheck"
12
13
  module Dhall
13
14
  using Dhall::AsDhall
14
15
 
15
- def self.load(source, resolver: Resolvers::Default.new)
16
+ def self.load(
17
+ source,
18
+ resolver: Resolvers::Default.new,
19
+ timeout: 10
20
+ )
21
+ deadline = Util::Deadline.for_timeout(timeout)
16
22
  Promise.resolve(nil).then {
17
- load_raw(source).resolve(resolver: resolver)
23
+ load_raw(source.to_s, timeout: timeout).resolve(
24
+ resolver: resolver.with_deadline(deadline)
25
+ )
18
26
  }.then do |resolved|
19
- TypeChecker.for(resolved).annotate(TypeChecker::Context.new).normalize
27
+ deadline.timeout_block do
28
+ TypeChecker.for(resolved).annotate(TypeChecker::Context.new).normalize
29
+ end
20
30
  end
21
31
  end
22
32
 
23
- def self.load_raw(source)
24
- begin
25
- return from_binary(source) if source.encoding == Encoding::BINARY
26
- rescue Exception # rubocop:disable Lint/RescueException
27
- # Parsing CBOR failed, so guess this is source text in standard UTF-8
28
- return load_raw(source.force_encoding("UTF-8"))
29
- end
33
+ def self.load_raw(source, timeout: 10)
34
+ source = Util.text_or_binary(source)
30
35
 
31
- Parser.parse(source.encode("UTF-8")).value
36
+ Util::Deadline.for_timeout(timeout).timeout_block do
37
+ if source.encoding == Encoding::BINARY
38
+ from_binary(source)
39
+ else
40
+ Parser.parse(source).value
41
+ end
42
+ end
32
43
  end
33
44
 
34
45
  def self.dump(o)
35
46
  CBOR.encode(o.as_dhall)
36
47
  end
48
+
49
+ class TimeoutException < StandardError; end
37
50
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ostruct"
4
+ require "psych"
4
5
 
5
6
  module Dhall
6
7
  module AsDhall
@@ -35,7 +36,21 @@ module Dhall
35
36
 
36
37
  refine ::String do
37
38
  def as_dhall
38
- Text.new(value: self)
39
+ if encoding == Encoding::BINARY
40
+ bytes.as_dhall
41
+ else
42
+ Text.new(value: self)
43
+ end
44
+ end
45
+ end
46
+
47
+ refine ::Symbol do
48
+ def as_dhall
49
+ Dhall::Union.new(
50
+ tag: to_s,
51
+ value: nil,
52
+ alternatives: Dhall::UnionType.new(alternatives: {})
53
+ )
39
54
  end
40
55
  end
41
56
 
@@ -165,29 +180,38 @@ module Dhall
165
180
 
166
181
  refine ::OpenStruct do
167
182
  def as_dhall
168
- annotation = TypeChecker
169
- .for(to_h.as_dhall)
170
- .annotate(TypeChecker::Context.new)
171
- Union.new(
172
- tag: "OpenStruct",
173
- value: annotation,
174
- alternatives: UnionType.new(alternatives: {})
183
+ expr = to_h.as_dhall
184
+ type = TypeChecker.for(expr).annotate(TypeChecker::Context.new).type
185
+ Union.from(
186
+ UnionType.new(alternatives: { "OpenStruct" => type }),
187
+ "OpenStruct",
188
+ expr
175
189
  )
176
190
  end
177
191
  end
178
192
 
179
- refine ::Object do
193
+ refine ::Psych::Coder do
180
194
  def as_dhall
181
- ivars = instance_variables.each_with_object({}) { |ivar, h|
182
- h[ivar.to_s[1..-1]] = instance_variable_get(ivar)
183
- }.as_dhall
195
+ case type
196
+ when :seq
197
+ seq
198
+ when :map
199
+ map
200
+ else
201
+ scalar
202
+ end.as_dhall
203
+ end
204
+ end
184
205
 
185
- type = TypeChecker.for(ivars).annotate(TypeChecker::Context.new).type
206
+ refine ::Object do
207
+ def as_dhall
186
208
  tag = self.class.name
209
+ expr = Util.psych_coder_from(tag, self).as_dhall
210
+ type = TypeChecker.for(expr).annotate(TypeChecker::Context.new).type
187
211
  Union.from(
188
212
  UnionType.new(alternatives: { tag => type }),
189
213
  tag,
190
- ivars
214
+ expr
191
215
  )
192
216
  end
193
217
  end
data/lib/dhall/ast.rb CHANGED
@@ -41,10 +41,6 @@ module Dhall
41
41
  end
42
42
  end
43
43
 
44
- def <<(other)
45
- Operator::TextConcatenate.new(lhs: self, rhs: other)
46
- end
47
-
48
44
  def concat(other)
49
45
  Operator::ListConcatenate.new(lhs: self, rhs: other)
50
46
  end
@@ -118,9 +114,7 @@ module Dhall
118
114
  end)
119
115
 
120
116
  def self.for(function:, argument:)
121
- if function == Variable["Some"]
122
- Optional.new(value: argument)
123
- elsif function == Variable["None"]
117
+ if function == Builtins[:None]
124
118
  OptionalNone.new(value_type: argument)
125
119
  else
126
120
  new(function: function, argument: argument)
@@ -148,7 +142,7 @@ module Dhall
148
142
 
149
143
  class Function < Expression
150
144
  include(ValueSemantics.for_attributes do
151
- var Util::AllOf.new(::String, Util::Not.new(Util::BuiltinName))
145
+ var ::String
152
146
  type Either(nil, Expression) # nil is not allowed in proper Dhall
153
147
  body Expression
154
148
  end)
@@ -234,6 +228,10 @@ module Dhall
234
228
  def as_json
235
229
  value
236
230
  end
231
+
232
+ def self.as_dhall
233
+ Builtins[:Bool]
234
+ end
237
235
  end
238
236
 
239
237
  class Variable < Expression
@@ -253,8 +251,6 @@ module Dhall
253
251
  def as_json
254
252
  if name == "_"
255
253
  index
256
- elsif index.zero?
257
- name
258
254
  else
259
255
  [name, index]
260
256
  end
@@ -309,9 +305,13 @@ module Dhall
309
305
  end
310
306
  end
311
307
 
308
+ def self.as_dhall
309
+ Builtins[:List]
310
+ end
311
+
312
312
  def type
313
313
  Dhall::Application.new(
314
- function: Dhall::Variable["List"],
314
+ function: self.class.as_dhall,
315
315
  argument: element_type
316
316
  )
317
317
  end
@@ -321,7 +321,10 @@ module Dhall
321
321
  end
322
322
 
323
323
  def map(type: nil, &block)
324
- with(elements: elements.each_with_index.map(&block), element_type: type)
324
+ with(
325
+ elements: elements.each_with_index.map(&block),
326
+ element_type: type&.as_dhall
327
+ )
325
328
  end
326
329
 
327
330
  def each(&block)
@@ -426,6 +429,10 @@ module Dhall
426
429
  end
427
430
  end
428
431
 
432
+ def self.as_dhall
433
+ Builtins[:Natural]
434
+ end
435
+
429
436
  def initialize(normalized: false, **attrs)
430
437
  @normalized = normalized
431
438
  super(**attrs)
@@ -435,7 +442,7 @@ module Dhall
435
442
  return unless value_type
436
443
 
437
444
  Dhall::Application.new(
438
- function: Dhall::Variable["Optional"],
445
+ function: Builtins[:Optional],
439
446
  argument: value_type
440
447
  )
441
448
  end
@@ -462,6 +469,10 @@ module Dhall
462
469
  value_type Expression
463
470
  end)
464
471
 
472
+ def self.as_dhall
473
+ Builtins[:None]
474
+ end
475
+
465
476
  def map(type: nil)
466
477
  type.nil? ? self : with(value_type: type)
467
478
  end
@@ -475,7 +486,10 @@ module Dhall
475
486
  end
476
487
 
477
488
  def as_json
478
- [0, Variable["None"].as_json, value_type.as_json]
489
+ Application.new(
490
+ function: self.class.as_dhall,
491
+ argument: value_type
492
+ ).as_json
479
493
  end
480
494
  end
481
495
 
@@ -780,10 +794,9 @@ module Dhall
780
794
  end
781
795
 
782
796
  def get_constructor(selector)
783
- var = Util::BuiltinName === selector ? "_" : selector
784
797
  type = alternatives.fetch(selector)
785
- body = Union.from(self, selector, Variable[var])
786
- Function.new(var: var, type: type, body: body)
798
+ body = Union.from(self, selector, Variable[selector])
799
+ Function.new(var: selector, type: type, body: body)
787
800
  end
788
801
 
789
802
  def constructor_types
@@ -791,8 +804,7 @@ module Dhall
791
804
  ctypes[k] = if type.nil?
792
805
  self
793
806
  else
794
- var = Util::BuiltinName === k ? "_" : k
795
- Forall.new(var: var, type: type, body: self)
807
+ Forall.new(var: k, type: type, body: self)
796
808
  end
797
809
  end
798
810
  end
@@ -893,6 +905,10 @@ module Dhall
893
905
  value (0..Float::INFINITY)
894
906
  end)
895
907
 
908
+ def self.as_dhall
909
+ Builtins[:Natural]
910
+ end
911
+
896
912
  def coerce(other)
897
913
  [other.as_dhall, self]
898
914
  end
@@ -954,6 +970,10 @@ module Dhall
954
970
  value ::Integer
955
971
  end)
956
972
 
973
+ def self.as_dhall
974
+ Builtins[:Integer]
975
+ end
976
+
957
977
  def to_s
958
978
  "#{value >= 0 ? "+" : ""}#{value}"
959
979
  end
@@ -976,6 +996,10 @@ module Dhall
976
996
  value ::Float
977
997
  end)
978
998
 
999
+ def self.as_dhall
1000
+ Builtins[:Double]
1001
+ end
1002
+
979
1003
  def to_s
980
1004
  value.to_s
981
1005
  end
@@ -1030,6 +1054,10 @@ module Dhall
1030
1054
  value ::String, coerce: ->(s) { s.encode("UTF-8") }
1031
1055
  end)
1032
1056
 
1057
+ def self.as_dhall
1058
+ Builtins[:Text]
1059
+ end
1060
+
1033
1061
  def <<(other)
1034
1062
  if other.is_a?(Text)
1035
1063
  with(value: value + other.value)
@@ -1115,13 +1143,6 @@ module Dhall
1115
1143
  query Either(nil, ::String)
1116
1144
  end)
1117
1145
 
1118
- HeaderType = RecordType.new(
1119
- record: {
1120
- "header" => Variable["Text"],
1121
- "value" => Variable["Text"]
1122
- }
1123
- )
1124
-
1125
1146
  def initialize(headers, authority, *path, query)
1126
1147
  super(
1127
1148
  headers: headers,
@@ -1151,7 +1172,14 @@ module Dhall
1151
1172
  end
1152
1173
 
1153
1174
  def headers
1154
- super || EmptyList.new(element_type: HeaderType)
1175
+ header_type = RecordType.new(
1176
+ record: {
1177
+ "header" => Builtins[:Text],
1178
+ "value" => Builtins[:Text]
1179
+ }
1180
+ )
1181
+
1182
+ super || EmptyList.new(element_type: header_type)
1155
1183
  end
1156
1184
 
1157
1185
  def uri
@@ -1225,13 +1253,15 @@ module Dhall
1225
1253
  end
1226
1254
 
1227
1255
  def self.from_string(s)
1228
- parts = s.to_s.split(/\//)
1229
- if parts.first == ""
1230
- AbsolutePath.new(*parts[1..-1])
1231
- elsif parts.first == "~"
1232
- RelativeToHomePath.new(*parts[1..-1])
1256
+ prefix, *suffix = s.to_s.split(/\//)
1257
+ if prefix == ""
1258
+ AbsolutePath.new(*suffix)
1259
+ elsif prefix == "~"
1260
+ RelativeToHomePath.new(*suffix)
1261
+ elsif prefix == ".."
1262
+ RelativeToParentPath.new(*suffix)
1233
1263
  else
1234
- RelativePath.new(*parts)
1264
+ RelativePath.new(prefix, *suffix)
1235
1265
  end
1236
1266
  end
1237
1267
 
@@ -1303,7 +1333,7 @@ module Dhall
1303
1333
  Pathname.new("~").join(*@path)
1304
1334
  end
1305
1335
 
1306
- def chain_onto(*)
1336
+ def chain_onto(relative_to)
1307
1337
  if relative_to.is_a?(URI)
1308
1338
  raise ImportBannedException, "remote import cannot import #{self}"
1309
1339
  end
@@ -1331,6 +1361,8 @@ module Dhall
1331
1361
  end
1332
1362
  end
1333
1363
 
1364
+ attr_reader :var
1365
+
1334
1366
  def initialize(var)
1335
1367
  @var = var
1336
1368
  end
@@ -1340,28 +1372,27 @@ module Dhall
1340
1372
  raise ImportBannedException, "remote import cannot import #{self}"
1341
1373
  end
1342
1374
 
1343
- real_path.chain_onto(relative_to)
1375
+ self
1376
+ end
1377
+
1378
+ def path
1379
+ []
1380
+ end
1381
+
1382
+ def with(path:)
1383
+ Path.from_string(path.join("/"))
1344
1384
  end
1345
1385
 
1346
1386
  def canonical
1347
- real_path.canonical
1387
+ self
1348
1388
  end
1349
1389
 
1350
1390
  def real_path
1351
- val = ENV.fetch(@var) do
1352
- raise ImportFailedException, "No #{self}"
1353
- end
1354
- if val =~ /\Ahttps?:\/\//
1355
- URI.from_uri(URI(val))
1356
- else
1357
- Path.from_string(val)
1358
- end
1391
+ self
1359
1392
  end
1360
1393
 
1361
1394
  def resolve(resolver)
1362
- Promise.resolve(nil).then do
1363
- real_path.resolve(resolver)
1364
- end
1395
+ resolver.resolve_environment(self)
1365
1396
  end
1366
1397
 
1367
1398
  def origin
@@ -1372,6 +1403,15 @@ module Dhall
1372
1403
  "env:#{as_json}"
1373
1404
  end
1374
1405
 
1406
+ def hash
1407
+ @var.hash
1408
+ end
1409
+
1410
+ def eql?(other)
1411
+ other.is_a?(self.class) && other.var == var
1412
+ end
1413
+ alias eql? ==
1414
+
1375
1415
  def as_json
1376
1416
  @var.gsub(/[\"\\\a\b\f\n\r\t\v]/) do |c|
1377
1417
  "\\" + ESCAPES.find { |(_, v)| v == c }.first
@@ -1404,13 +1444,15 @@ module Dhall
1404
1444
  end
1405
1445
 
1406
1446
  class Expression
1407
- def self.call(import_value)
1408
- Dhall.load_raw(import_value)
1447
+ def self.call(import_value, deadline: Util::NoDeadline.new)
1448
+ return import_value if import_value.is_a?(Dhall::Expression)
1449
+
1450
+ Dhall.load_raw(import_value, timeout: deadline.timeout)
1409
1451
  end
1410
1452
  end
1411
1453
 
1412
1454
  class Text
1413
- def self.call(import_value)
1455
+ def self.call(import_value, deadline: Util::NoDeadline.new)
1414
1456
  Dhall::Text.new(value: import_value)
1415
1457
  end
1416
1458
  end
@@ -1452,13 +1494,13 @@ module Dhall
1452
1494
  path.chain_onto(relative_to).canonical
1453
1495
  end
1454
1496
 
1455
- def parse_and_check(raw)
1456
- integrity_check.check(import_type.call(raw))
1497
+ def parse_and_check(raw, deadline: Util::NoDeadline.new)
1498
+ integrity_check.check(import_type.call(raw, deadline: deadline))
1457
1499
  end
1458
1500
 
1459
1501
  def cache_key(relative_to)
1460
1502
  if integrity_check.protocol == :nocheck
1461
- real_path(relative_to).to_s
1503
+ real_path(relative_to)
1462
1504
  else
1463
1505
  integrity_check.to_s
1464
1506
  end
@@ -1477,7 +1519,7 @@ module Dhall
1477
1519
 
1478
1520
  class Let < Expression
1479
1521
  include(ValueSemantics.for_attributes do
1480
- var Util::AllOf.new(::String, Util::Not.new(Util::BuiltinName))
1522
+ var ::String
1481
1523
  assign Expression
1482
1524
  type Either(nil, Expression)
1483
1525
  end)