~singpolyma/dhall-ruby

4601095e742bd3f15819a7629706b0b9f7196af7 — Stephen Paul Weber 3 years ago 4752d63
Parse expression from ENV var import

Instead of the path-only support from before.
4 files changed, 118 insertions(+), 41 deletions(-)

M lib/dhall/ast.rb
M lib/dhall/resolve.rb
M test/test_resolve.rb
M test/test_resolvers.rb
M lib/dhall/ast.rb => lib/dhall/ast.rb +32 -20
@@ 1225,13 1225,15 @@ module Dhall
			end

			def self.from_string(s)
				parts = s.to_s.split(/\//)
				if parts.first == ""
					AbsolutePath.new(*parts[1..-1])
				elsif parts.first == "~"
					RelativeToHomePath.new(*parts[1..-1])
				prefix, *suffix = s.to_s.split(/\//)
				if prefix == ""
					AbsolutePath.new(*suffix)
				elsif prefix == "~"
					RelativeToHomePath.new(*suffix)
				elsif prefix == ".."
					RelativeToParentPath.new(*suffix)
				else
					RelativePath.new(*parts)
					RelativePath.new(prefix, *suffix)
				end
			end



@@ 1303,7 1305,7 @@ module Dhall
				Pathname.new("~").join(*@path)
			end

			def chain_onto(*)
			def chain_onto(relative_to)
				if relative_to.is_a?(URI)
					raise ImportBannedException, "remote import cannot import #{self}"
				end


@@ 1331,6 1333,8 @@ module Dhall
				end
			end

			attr_reader :var

			def initialize(var)
				@var = var
			end


@@ 1340,28 1344,27 @@ module Dhall
					raise ImportBannedException, "remote import cannot import #{self}"
				end

				real_path.chain_onto(relative_to)
				self
			end

			def path
				[]
			end

			def with(path:)
				Path.from_string(path.join("/"))
			end

			def canonical
				real_path.canonical
				self
			end

			def real_path
				val = ENV.fetch(@var) do
					raise ImportFailedException, "No #{self}"
				end
				if val =~ /\Ahttps?:\/\//
					URI.from_uri(URI(val))
				else
					Path.from_string(val)
				end
				self
			end

			def resolve(resolver)
				Promise.resolve(nil).then do
					real_path.resolve(resolver)
				end
				resolver.resolve_environment(self)
			end

			def origin


@@ 1372,6 1375,15 @@ module Dhall
				"env:#{as_json}"
			end

			def hash
				@var.hash
			end

			def eql?(other)
				other.is_a?(self.class) && other.var == var
			end
			alias eql? ==

			def as_json
				@var.gsub(/[\"\\\a\b\f\n\r\t\v]/) do |c|
					"\\" + ESCAPES.find { |(_, v)| v == c }.first

M lib/dhall/resolve.rb => lib/dhall/resolve.rb +44 -14
@@ 20,6 20,16 @@ module Dhall
			end
		end

		ReadEnvironmentSources = lambda do |sources|
			sources.map do |source|
				Promise.resolve(nil).then do
					ENV.fetch(source.var) do
						raise ImportFailedException, "No #{source}"
					end
				end
			end
		end

		PreflightCORS = lambda do |source, parent_origin|
			uri = source.uri
			if parent_origin != "localhost" && parent_origin != source.origin


@@ 175,11 185,13 @@ module Dhall
			def initialize(
				path_reader: ReadPathSources,
				http_reader: StandardReadHttpSources,
				https_reader: http_reader
				https_reader: http_reader,
				environment_reader: ReadEnvironmentSources
			)
				@path_resolutions = ResolutionSet.new(path_reader)
				@http_resolutions = ResolutionSet.new(http_reader)
				@https_resolutions = ResolutionSet.new(https_reader)
				@env_resolutions = ResolutionSet.new(environment_reader)
				@cache = {}
			end



@@ 195,6 207,10 @@ module Dhall
				@path_resolutions.register(path_source)
			end

			def resolve_environment(env_source)
				@env_resolutions.register(env_source)
			end

			def resolve_http(http_source)
				http_source.headers.resolve(
					resolver:    self,


@@ 220,6 236,7 @@ module Dhall
			def finish!
				[
					@path_resolutions,
					@env_resolutions,
					@http_resolutions,
					@https_resolutions
				].each do |rset|


@@ 232,6 249,7 @@ module Dhall
				dup.tap do |c|
					c.instance_eval do
						@path_resolutions = @path_resolutions.child(parent_source)
						@env_resolutions = @env_resolutions.child(parent_source)
						@http_resolutions = @http_resolutions.child(parent_source)
						@https_resolutions = @https_resolutions.child(parent_source)
					end


@@ 244,27 262,32 @@ module Dhall
				path_reader: ReadPathSources,
				http_reader: ReadHttpSources,
				https_reader: http_reader,
				environment_reader: ReadEnvironmentSources,
				ipfs_public_gateway: "cloudflare-ipfs.com"
			)
				super(
					path_reader:  ReadPathAndIPFSSources.new(
					path_reader: ReadPathAndIPFSSources.new(
						path_reader:    path_reader,
						http_reader:    http_reader,
						https_reader:   https_reader,
						public_gateway: ipfs_public_gateway
					),
					http_reader:  http_reader,
					https_reader: https_reader
					http_reader: http_reader, https_reader: https_reader,
					environment_reader: environment_reader
				)
			end
		end

		class LocalOnly < Standard
			def initialize(path_reader: ReadPathSources)
			def initialize(
				path_reader: ReadPathSources,
				environment_reader: ReadEnvironmentSources
			)
				super(
					path_reader:  path_reader,
					http_reader:  RejectSources,
					https_reader: RejectSources
					path_reader:        path_reader,
					environment_reader: environment_reader,
					http_reader:        RejectSources,
					https_reader:       RejectSources
				)
			end
		end


@@ 272,9 295,10 @@ module Dhall
		class None < Default
			def initialize
				super(
					path_reader:  RejectSources,
					http_reader:  RejectSources,
					https_reader: RejectSources
					path_reader:        RejectSources,
					environment_reader: RejectSources,
					http_reader:        RejectSources,
					https_reader:       RejectSources
				)
			end
		end


@@ 328,9 352,15 @@ module Dhall
		class FallbackResolver < ExpressionResolver
			register_for Operator::ImportFallback

			def resolve(**kwargs)
				ExpressionResolver.for(@expr.lhs).resolve(**kwargs).catch do
					ExpressionResolver.for(@expr.rhs).resolve(**kwargs)
			def resolve(resolver:, relative_to:)
				ExpressionResolver.for(@expr.lhs).resolve(
					resolver:    resolver,
					relative_to: relative_to
				).catch do
					@expr.rhs.resolve(
						resolver:    resolver.child(Import::MissingImport.new),
						relative_to: relative_to
					)
				end
			end
		end

M test/test_resolve.rb => test/test_resolve.rb +39 -1
@@ 10,7 10,7 @@ class TestResolve < Minitest::Test
	def setup
		@relative_to = Dhall::Import::RelativePath.new
		@resolver = Dhall::Resolvers::Default.new(
			path_reader: lambda do |sources|
			path_reader:        lambda do |sources|
				sources.map do |source|
					Promise.resolve(Base64.decode64({
						"var"      => "AA",


@@ 25,6 25,14 @@ class TestResolve < Minitest::Test
						"headers"  => "gwT2ggiiZmhlYWRlcoISYnRoZXZhbHVlghJidHY"
					}.fetch(source.pathname.to_s)))
				end
			end,
			environment_reader: lambda do |sources|
				sources.map do |source|
					Promise.resolve({
						"NAT"  => "1",
						"PATH" => "./var"
					}.fetch(source.var))
				end
			end
		)
	end


@@ 195,6 203,36 @@ class TestResolve < Minitest::Test
		assert_equal Dhall::Variable["_"], subject(expr)
	end

	def test_env_natural
		expr = Dhall::Import.new(
			Dhall::Import::IntegrityCheck.new,
			Dhall::Import::Expression,
			Dhall::Import::EnvironmentVariable.new("NAT")
		)

		assert_equal Dhall::Natural.new(value: 1), subject(expr)
	end

	def test_env_as_text
		expr = Dhall::Import.new(
			Dhall::Import::IntegrityCheck.new,
			Dhall::Import::Text,
			Dhall::Import::EnvironmentVariable.new("NAT")
		)

		assert_equal Dhall::Text.new(value: "1"), subject(expr)
	end

	def test_env_relative
		expr = Dhall::Import.new(
			Dhall::Import::IntegrityCheck.new,
			Dhall::Import::Expression,
			Dhall::Import::EnvironmentVariable.new("PATH")
		)

		assert_equal Dhall::Variable["_"], subject(expr)
	end

	def test_ipfs
		stub_request(:get, "http://localhost:8000/ipfs/TESTCID")
			.to_return(status: 200, body: "\x00".b)

M test/test_resolvers.rb => test/test_resolvers.rb +3 -6
@@ 18,19 18,16 @@ class TestResolvers < Minitest::Test
		assert_equal source, promise.sync
	end

	def test_default_resolver_path_from_env
		ENV["__DHALL_IMPORT_TEST"] = "/dhall/common/x.dhall"
	def test_default_resolver_env
		resolver = Dhall::Resolvers::Default.new(
			path_reader: lambda do |sources|
			environment_reader: lambda do |sources|
				sources.map { |source| Promise.resolve(source) }
			end
		)
		source = Dhall::Import::EnvironmentVariable.new("__DHALL_IMPORT_TEST")
		promise = source.resolve(resolver)
		resolver.finish!

		expected = Dhall::Import::AbsolutePath.new("dhall", "common", "x.dhall")
		assert_equal expected, promise.sync
		assert_equal source, promise.sync
	end

	def test_default_resolver_http