dhall 0.1.0 → 0.5.1

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
- SHA1:
3
- metadata.gz: aaa68fc11b3223d666fd199cf507d4ca5744e31d
4
- data.tar.gz: 0b3643c8d75220906f9ac34b08e47c613bb1efb5
2
+ SHA256:
3
+ metadata.gz: 5b0efbcf23922a25863898fa6ac51fc625f5ecd1590f7b95858ea736de8e091b
4
+ data.tar.gz: 96685b1fca760158f9a84ca6c7b8a8fdc1d0203c1f25af94d989c492af82e533
5
5
  SHA512:
6
- metadata.gz: c5225d59efa8144191b163ec0cc565ec78846d7107f479e4dccc78056ff38a744865e86c857cfedf0808f836c292044bed91aaa8c4b74b844b9f380fc14da366
7
- data.tar.gz: 675ae2377aabb52b68a6f5db83eb8ebab10b9a44f13d5025908b1d6adc2f8a20b25ea4e74e7b48adaf41ae982e23bc5f3e515c8572667717fa7ed21bf4666569
6
+ metadata.gz: 1785d0b615378c128b3729b6d4d11df4f4c8da9beb07435b265cf1ff8f38583e4abb1ee5877029c474451acd465df1361175fdb8fab0d32358740765f339072e
7
+ data.tar.gz: 1ab4e4a5bc480ca0774429ce56d6007c5efc8e3009e8219c91c71b18821e2453573d23d1ece119ebf9c36e369cacf0ffeb1d681e869044e98200354feed04d63
data/README.md CHANGED
@@ -6,13 +6,13 @@ 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
 
13
13
  Add this line to your application's Gemfile:
14
14
 
15
- gem 'dhall'
15
+ gem "dhall"
16
16
 
17
17
  And then execute:
18
18
 
@@ -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:
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "dhall"
5
+ require "optparse"
6
+
7
+ @extension = ".dhallb"
8
+
9
+ def compile(source)
10
+ Dhall.load(
11
+ source,
12
+ timeout: Float::INFINITY,
13
+ resolver: Dhall::Resolvers::Default.new(
14
+ max_depth: Float::INFINITY
15
+ )
16
+ )
17
+ end
18
+
19
+ module FilenameWriter
20
+ def self.write(_, out, dhall)
21
+ warn out
22
+ out.dirname.mkpath
23
+ out.write(dhall.to_binary)
24
+ end
25
+ end
26
+
27
+ module CacheWriter
28
+ def self.write(output_directory, out, dhall)
29
+ base = "1220#{dhall.digest.hexdigest}"
30
+ out = out.dirname + base
31
+ if output_directory
32
+ out = output_directory + base
33
+ out.dirname.mkpath
34
+ end
35
+ warn out
36
+ out.write(dhall.to_cbor)
37
+ end
38
+ end
39
+
40
+ def compile_file(file_path, relative_to: Pathname.new("."))
41
+ $stderr.print "#{file_path} => "
42
+ out = file_path.sub_ext(@extension)
43
+ if @output_directory
44
+ out = @output_directory + out.relative_path_from(relative_to)
45
+ end
46
+ compile(file_path.expand_path).then do |dhall|
47
+ @writer.write(@output_directory, out, dhall)
48
+ end
49
+ end
50
+
51
+ @writer = FilenameWriter
52
+ # rubocop:disable Metrics/BlockLength
53
+ opt_parser = OptionParser.new do |opts|
54
+ opts.banner = "Usage: dhall-compile [options] [-] [files_and_dirs]"
55
+
56
+ opts.on(
57
+ "-oDIRECTORY",
58
+ "--output-directory DIRECTORY",
59
+ "Write output to this directory"
60
+ ) do |dir|
61
+ @output_directory = Pathname.new(dir)
62
+ end
63
+
64
+ opts.on(
65
+ "-e [EXTENSION]",
66
+ "--extension [EXTENSION]",
67
+ "Use this extension for files (default .dhallb)"
68
+ ) do |ext|
69
+ @extension = ext ? ".#{ext}" : ""
70
+ end
71
+
72
+ opts.on(
73
+ "-c",
74
+ "--cache",
75
+ "Write output in standard dhall file cache format"
76
+ ) do
77
+ @extension = ""
78
+ @writer = CacheWriter
79
+ end
80
+
81
+ opts.on("-h", "--help", "Show this usage information") do
82
+ warn opts
83
+ exit
84
+ end
85
+ end
86
+ # rubocop:enable Metrics/BlockLength
87
+
88
+ opt_parser.parse!
89
+
90
+ if ARGV.empty?
91
+ warn opt_parser
92
+ exit 0
93
+ end
94
+
95
+ ARGV.map(&Pathname.method(:new)).each do |path|
96
+ if !path.exist? && path.to_s == "-"
97
+ warn "Compiling STDIN to STDOUT"
98
+ compile(STDIN.read).then(&STDOUT.method(:write)).sync
99
+ elsif path.file?
100
+ compile_file(path, relative_to: path.dirname).sync
101
+ elsif path.directory?
102
+ warn "Recursively compiling #{path}"
103
+ path.find.flat_map do |child|
104
+ next if !child.file? || child.extname == ".dhallb"
105
+ compile_file(child, relative_to: path).sync
106
+ end
107
+ else
108
+ warn "#{path} may not exist"
109
+ exit 1
110
+ end
111
+ end
@@ -5,4 +5,4 @@ require "dhall"
5
5
  require "json"
6
6
  using Dhall::AsDhall
7
7
 
8
- STDOUT.write(CBOR.encode(JSON.parse(STDIN.read).as_dhall))
8
+ STDOUT.write(JSON.parse(STDIN.read).as_dhall.to_binary)
@@ -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(YAML.safe_load(STDIN.read, [Symbol]).as_dhall.to_binary)
@@ -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"
@@ -25,12 +25,16 @@ Gem::Specification.new do |spec|
25
25
  spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
26
26
  spec.require_paths = ["lib"]
27
27
 
28
+ spec.add_dependency "base32", "~> 0.3.2"
28
29
  spec.add_dependency "cbor", "~> 0.5.9.3"
29
30
  spec.add_dependency "citrus", "~> 3.0"
31
+ spec.add_dependency "lazy_object", "~> 0.0.3"
32
+ spec.add_dependency "multihashes", "~> 0.1.3"
30
33
  spec.add_dependency "promise.rb", "~> 0.7.4"
31
34
  spec.add_dependency "value_semantics", "~> 3.0"
32
35
 
33
36
  spec.add_development_dependency "abnf", "~> 0.0.1"
37
+ spec.add_development_dependency "minitest-fail-fast", "~> 0.1.0"
34
38
  spec.add_development_dependency "simplecov", "~> 0.16.1"
35
39
  spec.add_development_dependency "webmock", "~> 3.5"
36
40
  end
@@ -4,34 +4,48 @@ 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"
10
11
  require "dhall/typecheck"
12
+ require "dhall/types"
11
13
 
12
14
  module Dhall
13
15
  using Dhall::AsDhall
14
16
 
15
- def self.load(source, resolver: Resolvers::Default.new)
17
+ def self.load(
18
+ source,
19
+ resolver: Resolvers::Default.new,
20
+ timeout: 10
21
+ )
22
+ deadline = Util::Deadline.for_timeout(timeout)
16
23
  Promise.resolve(nil).then {
17
- load_raw(source).resolve(resolver: resolver)
24
+ load_raw(source.to_s, timeout: timeout).resolve(
25
+ resolver: resolver.with_deadline(deadline)
26
+ )
18
27
  }.then do |resolved|
19
- TypeChecker.for(resolved).annotate(TypeChecker::Context.new).normalize
28
+ deadline.timeout_block do
29
+ TypeChecker.for(resolved).annotate(TypeChecker::Context.new).normalize
30
+ end
20
31
  end
21
32
  end
22
33
 
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
34
+ def self.load_raw(source, timeout: 10)
35
+ source = Util.text_or_binary(source)
30
36
 
31
- Parser.parse(source.encode("UTF-8")).value
37
+ Util::Deadline.for_timeout(timeout).timeout_block do
38
+ if source.encoding == Encoding::BINARY
39
+ from_binary(source)
40
+ else
41
+ Parser.parse(source).value
42
+ end
43
+ end
32
44
  end
33
45
 
34
46
  def self.dump(o)
35
47
  CBOR.encode(o.as_dhall)
36
48
  end
49
+
50
+ class TimeoutException < StandardError; end
37
51
  end
@@ -1,41 +1,108 @@
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
7
8
  TAGS = {
8
- ::Integer => "Integer",
9
+ ::Array => "List",
9
10
  ::FalseClass => "Bool",
10
- ::Integer => "Integer",
11
11
  ::Float => "Double",
12
+ ::Hash => "Record",
13
+ ::Integer => "Integer",
14
+ ::Integer => "Integer",
12
15
  ::NilClass => "None",
13
16
  ::String => "Text",
14
17
  ::TrueClass => "Bool"
15
18
  }.freeze
16
19
 
17
- def self.tag_for(o, type)
20
+ def self.tag_for(o)
18
21
  return "Natural" if o.is_a?(::Integer) && !o.negative?
19
22
 
20
23
  TAGS.fetch(o.class) do
21
- "#{o.class.name}_#{type.digest.hexdigest}"
24
+ o.class.name
25
+ end
26
+ end
27
+
28
+ class AnnotatedExpressionList
29
+ attr_reader :type
30
+ attr_reader :exprs
31
+
32
+ def self.from(type_annotation)
33
+ if type_annotation.nil?
34
+ new(nil, [nil])
35
+ else
36
+ new(type_annotation.type, [type_annotation.value])
37
+ end
38
+ end
39
+
40
+ def initialize(type, exprs)
41
+ @type = type
42
+ @exprs = exprs
43
+ end
44
+
45
+ def +(other)
46
+ raise "#{type} != #{other.type}" if type != other.type
47
+ self.class.new(type, exprs + other.exprs)
22
48
  end
23
49
  end
24
50
 
25
- def self.union_of(values_and_types)
26
- z = [UnionType.new(alternatives: {}), []]
27
- values_and_types.reduce(z) do |(ut, tags), (v, t)|
28
- tag = tag_for(v, t)
29
- [
30
- ut.merge(UnionType.new(alternatives: { tag => t })),
31
- tags + [tag]
32
- ]
51
+ class UnionInferer
52
+ def initialize(tagged={})
53
+ @tagged = tagged
54
+ end
55
+
56
+ def union_type
57
+ UnionType.new(alternatives: Hash[@tagged.map { |k, v| [k, v.type] }])
58
+ end
59
+
60
+ def union_for(expr)
61
+ if expr.is_a?(Enum)
62
+ tag = expr.tag
63
+ expr = nil
64
+ else
65
+ tag = @tagged.keys.find { |k| @tagged[k].exprs.include?(expr) }
66
+ end
67
+ expr = expr.extract if expr.is_a?(Union)
68
+ Union.from(union_type, tag, expr)
69
+ end
70
+
71
+ def with(tag, type_annotation)
72
+ anno = AnnotatedExpressionList.from(type_annotation)
73
+ if @tagged.key?(tag) && @tagged[tag].type != anno.type
74
+ disambiguate_against(tag, anno)
75
+ else
76
+ self.class.new(@tagged.merge(tag => anno) { |_, x, y| x + y })
77
+ end
78
+ end
79
+
80
+ def disambiguate_against(tag, anno)
81
+ self.class.new(
82
+ @tagged.reject { |k, _| k == tag }.merge(
83
+ "#{tag}_#{@tagged[tag].type.digest.hexdigest}" => @tagged[tag],
84
+ "#{tag}_#{anno.type.digest.hexdigest}" => anno
85
+ )
86
+ )
33
87
  end
34
88
  end
35
89
 
36
90
  refine ::String do
37
91
  def as_dhall
38
- Text.new(value: self)
92
+ if encoding == Encoding::BINARY
93
+ bytes.as_dhall
94
+ else
95
+ Text.new(value: self)
96
+ end
97
+ end
98
+ end
99
+
100
+ refine ::Symbol do
101
+ def as_dhall
102
+ Dhall::Enum.new(
103
+ tag: to_s,
104
+ alternatives: Dhall::UnionType.new(alternatives: {})
105
+ )
39
106
  end
40
107
  end
41
108
 
@@ -128,17 +195,28 @@ module Dhall
128
195
 
129
196
  class Union
130
197
  def initialize(values, exprs, types)
131
- @values = values
198
+ @tags, @types = values.zip(types).map { |(value, type)|
199
+ if type.is_a?(UnionType) && type.alternatives.length == 1
200
+ type.alternatives.to_a.first
201
+ else
202
+ [AsDhall.tag_for(value), type]
203
+ end
204
+ }.transpose
132
205
  @exprs = exprs
133
- @types = types
206
+ @inferer = UnionInferer.new
134
207
  end
135
208
 
136
209
  def list
137
- ut, tags = AsDhall.union_of(@values.zip(@types))
138
-
139
- List.new(elements: @exprs.zip(tags).map do |(expr, tag)|
140
- Dhall::Union.from(ut, tag, expr)
141
- end)
210
+ final_inferer =
211
+ @tags
212
+ .zip(@exprs, @types)
213
+ .reduce(@inferer) do |inferer, (tag, expr, type)|
214
+ inferer.with(
215
+ tag,
216
+ type.nil? ? nil : TypeAnnotation.new(value: expr, type: type)
217
+ )
218
+ end
219
+ List.new(elements: @exprs.map(&final_inferer.method(:union_for)))
142
220
  end
143
221
  end
144
222
  end
@@ -165,31 +243,52 @@ module Dhall
165
243
 
166
244
  refine ::OpenStruct do
167
245
  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: {})
246
+ expr = to_h.as_dhall
247
+ type = TypeChecker.for(expr).annotate(TypeChecker::Context.new).type
248
+ Union.from(
249
+ UnionType.new(alternatives: { "OpenStruct" => type }),
250
+ "OpenStruct",
251
+ expr
175
252
  )
176
253
  end
177
254
  end
178
255
 
179
- refine ::Object do
256
+ refine ::Psych::Coder do
180
257
  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
258
+ case type
259
+ when :seq
260
+ seq
261
+ when :map
262
+ map
263
+ else
264
+ scalar
265
+ end.as_dhall
266
+ end
267
+ end
184
268
 
185
- type = TypeChecker.for(ivars).annotate(TypeChecker::Context.new).type
269
+ refine ::Object do
270
+ def as_dhall
186
271
  tag = self.class.name
272
+ expr = Util.psych_coder_from(tag, self).as_dhall
273
+ type = TypeChecker.for(expr).annotate(TypeChecker::Context.new).type
187
274
  Union.from(
188
275
  UnionType.new(alternatives: { tag => type }),
189
276
  tag,
190
- ivars
277
+ expr
191
278
  )
192
279
  end
193
280
  end
281
+
282
+ refine ::Proc do
283
+ def as_dhall
284
+ FunctionProxy.new(self)
285
+ end
286
+ end
287
+
288
+ refine ::Method do
289
+ def as_dhall
290
+ to_proc.as_dhall
291
+ end
292
+ end
194
293
  end
195
294
  end