litl 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Builder
5
+ # @api private
6
+ # @since 0.1.0
7
+ class Option
8
+ def initialize(mod, type)
9
+ @type_checker = TypeChecker.new(mod)
10
+ @type = type
11
+ end
12
+
13
+ def build
14
+ mod = Module.new do
15
+ include ::LIT::Object::Option
16
+ end
17
+
18
+ type_checker = @type_checker
19
+ type = @type
20
+
21
+ mod::Some.define_method(:check_type!) do |value|
22
+ # NOTE: actually covered, but SimpleCov cannot detect that
23
+ # :nocov:
24
+ type_checker.check_type!(type, value)
25
+ # :nocov:
26
+ end
27
+
28
+ mod
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ TypeError = Class.new(::TypeError)
7
+
8
+ InvalidASTError = Class.new(StandardError)
9
+ InvalidObjectError = Class.new(StandardError)
10
+ InvalidTypeError = Class.new(StandardError)
11
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ class Loader
7
+ FILE_EXTENSION = ".lit"
8
+
9
+ def initialize
10
+ loader = self
11
+
12
+ @target_module = Module.new do
13
+ include ::LIT::Object
14
+
15
+ define_singleton_method(:reload_file) do |file_path|
16
+ loader.reload_file(file_path)
17
+ end
18
+
19
+ define_singleton_method(:serialize) do |object|
20
+ Serializer.new(object).serialize
21
+ end
22
+
23
+ define_singleton_method(:deserialize_request) do |data|
24
+ RequestDeserializer.new(data, self).deserialize_request
25
+ end
26
+ end
27
+
28
+ @working_module = @target_module
29
+ end
30
+
31
+ def load_directory(dir_path)
32
+ glob = File.join("**", "*#{FILE_EXTENSION}")
33
+ @base_dir = Pathname.new(dir_path)
34
+
35
+ Dir.glob(glob, base: dir_path) do |filename|
36
+ load_file(filename)
37
+ end
38
+
39
+ @target_module
40
+ end
41
+
42
+ def reload_file(file_path)
43
+ @working_module = @target_module
44
+ load_file(file_path)
45
+ end
46
+
47
+ private
48
+
49
+ def load_file(filename)
50
+ file_path = Pathname.new(File.expand_path(filename, @base_dir))
51
+ relative_path = file_path.relative_path_from(@base_dir).to_s
52
+ namespace = [*File.dirname(relative_path).split(File::SEPARATOR).slice(1),
53
+ File.basename(relative_path, FILE_EXTENSION)]
54
+
55
+ if namespace == %w[main]
56
+ load_main_file(file_path)
57
+ else
58
+ load_regular_file(file_path, namespace)
59
+ end
60
+ end
61
+
62
+ def load_main_file(file_path)
63
+ ast = Parser.parse_file(file_path)
64
+ Builder::Object.new(ast, @target_module).build
65
+ end
66
+
67
+ def load_regular_file(file_path, namespace)
68
+ namespace.each do |mod|
69
+ @working_module = Utils.const_reset(@working_module, Utils.camelize(mod), Module.new)
70
+ end
71
+
72
+ ast = Parser.parse_file(file_path)
73
+
74
+ Builder::Object.new(ast, @working_module).build
75
+ @working_module.include(@target_module)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ module ModuleObserver
7
+ def self.included(object)
8
+ object.define_singleton_method(:observe) do |target_module|
9
+ namespace = target_module.name.split("::")
10
+
11
+ define_singleton_method(:const_missing) do |const_name|
12
+ mod = namespace.reduce(self) do |mod, child_name|
13
+ mod.const_get(child_name)
14
+ end
15
+
16
+ mod.const_get(const_name)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ module Object
7
+ require_relative "object/array"
8
+ require_relative "object/enum"
9
+ require_relative "object/enum_variant"
10
+ require_relative "object/map"
11
+ require_relative "object/option"
12
+ require_relative "object/struct"
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ class Array
8
+ def initialize(*values)
9
+ values.each { |v| check_type!(v) }
10
+ @values = values
11
+ end
12
+
13
+ def each(&block)
14
+ @values.each(&block)
15
+ end
16
+
17
+ def to_a
18
+ @values
19
+ end
20
+
21
+ def [](index)
22
+ @values[index]
23
+ end
24
+
25
+ def __values__
26
+ @values
27
+ end
28
+
29
+ private
30
+
31
+ def check_type!(value); end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ module Enum; end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ module EnumVariant; end
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ class Map
8
+ attr_reader :values
9
+
10
+ def initialize(values = {})
11
+ values = Utils::MapHash.new(values)
12
+ values.each { |k, v| check_type!(k, v) }
13
+ @values = values
14
+ end
15
+
16
+ def [](key)
17
+ @values[key]
18
+ end
19
+
20
+ def __values__
21
+ @values.to_h
22
+ end
23
+
24
+ private
25
+
26
+ def check_type!(key, value); end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ def self.included(object)
6
+ object.define_singleton_method(:Some) { |value| Option::Some.new(value) }
7
+ Utils.const_reset(object, "None", Option::None)
8
+ Utils.const_reset(object, "Some", Class.new(Option::Some))
9
+ end
10
+
11
+ # @api public
12
+ # @since 0.1.0
13
+ module Option
14
+ def self.included(object)
15
+ object.define_singleton_method(:Some) { |value| Some.new(value) }
16
+ Utils.const_reset(object, "Some", Class.new(Some))
17
+ end
18
+
19
+ None = Class.new
20
+
21
+ class Some
22
+ attr_reader :value
23
+
24
+ def initialize(value)
25
+ check_type!(value)
26
+ @value = value
27
+ end
28
+
29
+ def __value__
30
+ @value
31
+ end
32
+
33
+ private
34
+
35
+ def check_type!(value); end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ module Object
5
+ # @api public
6
+ # @since 0.1.0
7
+ class Struct; end
8
+ end
9
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ class RequestDeserializer
7
+ AST = Parser::AST
8
+
9
+ def initialize(raw_request, type_root)
10
+ # { action, payload }
11
+ @raw_request = raw_request
12
+ @type_root = type_root
13
+ end
14
+
15
+ def deserialize_request
16
+ action_namespace = @raw_request.fetch("action").split(".").map { |x| Utils.camelize(x) }
17
+ action_type = action_namespace.reduce(@type_root) do |object, namespace|
18
+ object.const_get(namespace)
19
+ end
20
+
21
+ request_type = action_type.const_get("Request")
22
+ @type_module = action_type.const_get("DefinedIn")
23
+
24
+ deserialize(request_type, @raw_request.fetch("payload"))
25
+ end
26
+
27
+ private
28
+
29
+ # rubocop:disable Metrics/MethodLength
30
+ def deserialize(type, payload)
31
+ if type.is_a?(Module)
32
+ if type <= Object::Struct
33
+ deserialize_struct(type, payload)
34
+ elsif type <= Object::Enum
35
+ deserialize_enum(type, payload)
36
+ elsif type == AST::Type::Primitive::String ||
37
+ type == AST::Type::Primitive::Integer ||
38
+ type == AST::Type::Primitive::Float ||
39
+ type == AST::Type::Primitive::Boolean
40
+ payload
41
+ else
42
+ deserialize(type::TYPE, payload)
43
+ end
44
+ elsif type.is_a?(AST::Type::Alias)
45
+ deserialize(@type_module.const_get(type.name), payload)
46
+ elsif type.is_a?(AST::Type::Primitive::Array)
47
+ deserialize_array(type, payload)
48
+ elsif type.is_a?(AST::Type::Primitive::Map)
49
+ deserialize_map(type, payload)
50
+ elsif type.is_a?(AST::Type::Primitive::Option)
51
+ deserialize_option(type.type, payload)
52
+ else
53
+ raise InvalidTypeError, "invalid type: #{type}"
54
+ end
55
+ end
56
+ # rubocop:enable Metrics/MethodLength
57
+
58
+ def deserialize_struct(struct_klass, struct_data)
59
+ params = struct_data.reduce({}) do |acc, (key, value)|
60
+ field_name = key.to_sym
61
+ field_type = struct_klass.__fields__[field_name]
62
+ acc.merge(field_name => deserialize(field_type, value))
63
+ end
64
+
65
+ struct_klass.new(params)
66
+ end
67
+
68
+ def deserialize_enum(enum_module, enum_data)
69
+ variant_name = Utils.camelize(enum_data.fetch("kind"))
70
+ variant = enum_module.const_get(variant_name)
71
+
72
+ if variant.__kind__ == :unit
73
+ variant
74
+ elsif variant.__kind__ == :struct
75
+ deserialize_struct(variant, enum_data.fetch("data"))
76
+ end
77
+ end
78
+
79
+ def deserialize_option(type, data)
80
+ if data.nil?
81
+ Object::Option::None
82
+ else
83
+ deserialized_data = deserialize(type, data)
84
+ option = Builder::Option.new(@type_module, type).build
85
+ option::Some.new(deserialized_data)
86
+ end
87
+ end
88
+
89
+ def deserialize_map(type, data)
90
+ map = Builder::Map.new(@type_module, type.key_type, type.value_type).build
91
+
92
+ deserialized_data = data.reduce({}) do |acc, (key, value)|
93
+ acc.merge(deserialize(type.key_type, key) => deserialize(type.value_type, value))
94
+ end
95
+
96
+ map.new(deserialized_data)
97
+ end
98
+
99
+ def deserialize_array(type, data)
100
+ array = Builder::Array.new(@type_module, type.type).build
101
+ array.new(*data.map { |x| deserialize(type.type, x) })
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LIT
4
+ # @api public
5
+ # @since 0.1.0
6
+ class Serializer
7
+ def initialize(object)
8
+ @object = object
9
+ end
10
+
11
+ def serialize
12
+ serialize_object(@object)
13
+ end
14
+
15
+ private
16
+
17
+ # rubocop:disable Metrics/MethodLength
18
+ def serialize_object(object)
19
+ if object.is_a?(Object::Struct)
20
+ if object.is_a?(Object::EnumVariant)
21
+ serialize_enum_variant(object)
22
+ else
23
+ serialize_struct(object)
24
+ end
25
+ elsif object.is_a?(Module) && object <= Object::EnumVariant
26
+ serialize_enum_variant(object)
27
+ elsif object.is_a?(Object::Array)
28
+ serialize_array(object)
29
+ elsif object.is_a?(Object::Map)
30
+ serialize_map(object)
31
+ elsif object.is_a?(Object::Option::Some) || object == Object::Option::None
32
+ serialize_option(object)
33
+ elsif object.is_a?(String) ||
34
+ object.is_a?(Integer) ||
35
+ object.is_a?(Float) ||
36
+ object == true ||
37
+ object == false
38
+ object
39
+ else
40
+ raise InvalidObjectError, "invalid object: #{object}"
41
+ end
42
+ end
43
+ # rubocop:enable Metrics/MethodLength
44
+
45
+ def serialize_struct(struct)
46
+ struct.instance_variables.reduce({}) do |hsh, ivar|
47
+ field_name = ivar.to_s.sub("@", "").to_sym
48
+ hsh.merge(field_name => serialize_object(struct.public_send(field_name)))
49
+ end
50
+ end
51
+
52
+ def serialize_enum_variant(variant)
53
+ data =
54
+ if variant.__kind__ == :struct
55
+ serialize_struct(variant)
56
+ else
57
+ nil
58
+ end
59
+
60
+ { kind: variant.__name__, data: data }
61
+ end
62
+
63
+ def serialize_array(array)
64
+ array.__values__.map { |value| serialize_object(value) }
65
+ end
66
+
67
+ def serialize_map(map)
68
+ map.__values__.reduce({}) do |acc, (key, value)|
69
+ acc.merge(serialize_object(key) => serialize_object(value))
70
+ end
71
+ end
72
+
73
+ def serialize_option(option)
74
+ if option == Object::Option::None
75
+ nil
76
+ else
77
+ serialize_object(option.__value__)
78
+ end
79
+ end
80
+ end
81
+ end