litl 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +23 -0
- data/.ruby-version +1 -0
- data/.travis.yml +19 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +89 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +21 -0
- data/bin/console +8 -0
- data/lib/lit.rb +22 -0
- data/lib/lit/builder.rb +12 -0
- data/lib/lit/builder/array.rb +27 -0
- data/lib/lit/builder/map.rb +30 -0
- data/lib/lit/builder/object.rb +175 -0
- data/lib/lit/builder/option.rb +32 -0
- data/lib/lit/errors.rb +11 -0
- data/lib/lit/loader.rb +78 -0
- data/lib/lit/module_observer.rb +21 -0
- data/lib/lit/object.rb +14 -0
- data/lib/lit/object/array.rb +34 -0
- data/lib/lit/object/enum.rb +9 -0
- data/lib/lit/object/enum_variant.rb +9 -0
- data/lib/lit/object/map.rb +29 -0
- data/lib/lit/object/option.rb +39 -0
- data/lib/lit/object/struct.rb +9 -0
- data/lib/lit/request_deserializer.rb +104 -0
- data/lib/lit/serializer.rb +81 -0
- data/lib/lit/type_checker.rb +106 -0
- data/lib/lit/utils.rb +44 -0
- data/lib/lit/version.rb +9 -0
- data/litl.gemspec +38 -0
- metadata +189 -0
@@ -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
|
data/lib/lit/errors.rb
ADDED
@@ -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
|
data/lib/lit/loader.rb
ADDED
@@ -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
|
data/lib/lit/object.rb
ADDED
@@ -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,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,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
|