typelizer 0.4.1 → 0.5.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 +4 -4
- data/CHANGELOG.md +23 -1
- data/README.md +156 -24
- data/lib/typelizer/config.rb +78 -59
- data/lib/typelizer/configuration.rb +159 -0
- data/lib/typelizer/contexts/scan_context.rb +20 -0
- data/lib/typelizer/contexts/writer_context.rb +89 -0
- data/lib/typelizer/dsl.rb +9 -11
- data/lib/typelizer/generator.rb +36 -16
- data/lib/typelizer/interface.rb +25 -11
- data/lib/typelizer/model_plugins/active_record.rb +61 -30
- data/lib/typelizer/property.rb +3 -2
- data/lib/typelizer/serializer_config_layer.rb +45 -0
- data/lib/typelizer/serializer_plugins/alba.rb +3 -2
- data/lib/typelizer/serializer_plugins/ams.rb +1 -1
- data/lib/typelizer/serializer_plugins/auto.rb +2 -2
- data/lib/typelizer/serializer_plugins/base.rb +3 -2
- data/lib/typelizer/serializer_plugins/oj_serializers.rb +2 -2
- data/lib/typelizer/serializer_plugins/panko.rb +1 -1
- data/lib/typelizer/version.rb +1 -1
- data/lib/typelizer/writer.rb +37 -9
- data/lib/typelizer.rb +23 -11
- metadata +6 -2
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Typelizer
|
4
|
+
# Context for a single writer during a generation pass.
|
5
|
+
# - Caches one Interface per serializer class (prevents duplicates/loops)
|
6
|
+
# - Computes per-serializer effective Config:
|
7
|
+
# library defaults < global (flat setters) < writer < DSL (parent → child)
|
8
|
+
class WriterContext
|
9
|
+
attr_reader :writer_config, :writer_name
|
10
|
+
|
11
|
+
def initialize(writer_name: nil, configuration: Typelizer.configuration)
|
12
|
+
@configuration = configuration
|
13
|
+
@writer_name = (writer_name || Configuration::DEFAULT_WRITER_NAME).to_sym
|
14
|
+
@writer_config = configuration.writer_config(@writer_name)
|
15
|
+
|
16
|
+
@interface_cache = {}
|
17
|
+
@config_cache = {}
|
18
|
+
@dsl_cache = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns a memoized Interface for the given serializer class within this writer context
|
22
|
+
# Guarantees a single Interface instance per serializer (in this context), which:
|
23
|
+
# - preserves object identity across associations,
|
24
|
+
# - prevents infinite loops on cyclic relations,
|
25
|
+
# - and avoids redundant recomputation
|
26
|
+
# The cache is scoped to WriterContext (i.e., per writer and per generation run)
|
27
|
+
def interface_for(serializer_class)
|
28
|
+
raise ArgumentError, "Serializer class cannot be nil" if serializer_class.nil?
|
29
|
+
|
30
|
+
@interface_cache[serializer_class] ||= Interface.new(
|
31
|
+
serializer: serializer_class,
|
32
|
+
context: self
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Resolves the effective configuration for a serializer class by merging
|
37
|
+
# configuration layers in priority order:
|
38
|
+
# Library defaults
|
39
|
+
# Global configuration settings
|
40
|
+
# Writer-specific configuration
|
41
|
+
# DSL configuration with inheritance (highest priority)
|
42
|
+
def config_for(serializer_class)
|
43
|
+
raise ArgumentError, "Serializer class cannot be nil" unless serializer_class
|
44
|
+
|
45
|
+
@config_cache[serializer_class] ||= build_config(serializer_class)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Builds the correct configuration by merging all configuration layers
|
51
|
+
def build_config(serializer_class)
|
52
|
+
global_settings = @configuration.global_settings
|
53
|
+
writer_settings = @writer_config.to_h
|
54
|
+
dsl_settings = dsl_config_for(serializer_class)
|
55
|
+
|
56
|
+
# Merge in priority order: global < writer < DSL
|
57
|
+
merged_config = deep_merge(global_settings, writer_settings)
|
58
|
+
merged_config = deep_merge(merged_config, dsl_settings)
|
59
|
+
|
60
|
+
Config.build(**merged_config).freeze
|
61
|
+
end
|
62
|
+
|
63
|
+
def dsl_config_for(klass)
|
64
|
+
return @dsl_cache[klass] if @dsl_cache.key?(klass)
|
65
|
+
|
66
|
+
# Recursively get the parent's DSL config. If no parent or parent is not
|
67
|
+
# a Typelizer serializer, the base is an empty hash.
|
68
|
+
parent_dsl = (parent = klass.superclass).respond_to?(:typelizer_config) ? dsl_config_for(parent) : {}
|
69
|
+
|
70
|
+
# Get this class's own local overrides.
|
71
|
+
local_dsl = klass.respond_to?(:typelizer_config) ? klass.typelizer_config.to_h : {}
|
72
|
+
|
73
|
+
@dsl_cache[klass] = deep_merge(parent_dsl, local_dsl).freeze
|
74
|
+
end
|
75
|
+
|
76
|
+
def deep_merge(hash_one, hash_two)
|
77
|
+
# If Active Support's `deep_merge` exists, use it
|
78
|
+
return hash_one.deep_merge(hash_two) if hash_one.respond_to?(:deep_merge)
|
79
|
+
|
80
|
+
return hash_one if hash_one == hash_two
|
81
|
+
return hash_one if hash_two.empty?
|
82
|
+
return hash_two if hash_one.empty?
|
83
|
+
|
84
|
+
hash_one.merge(hash_two) do |_, old_v, new_v|
|
85
|
+
(old_v.is_a?(Hash) && new_v.is_a?(Hash)) ? deep_merge(old_v, new_v) : new_v
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/typelizer/dsl.rb
CHANGED
@@ -14,18 +14,16 @@ module Typelizer
|
|
14
14
|
end
|
15
15
|
|
16
16
|
module ClassMethods
|
17
|
-
def typelizer_config
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@
|
25
|
-
end
|
17
|
+
def typelizer_config(&block)
|
18
|
+
# Lazily initializes and memoizes the hash for local overrides at the class level.
|
19
|
+
# This ensures that all subsequent DSL calls for this specific serializer class
|
20
|
+
# modify the same single hash, allowing settings to be accumulated
|
21
|
+
@serializer_overrides ||= {}
|
22
|
+
|
23
|
+
@config_layer ||= SerializerConfigLayer.new(@serializer_overrides)
|
24
|
+
@config_layer.instance_eval(&block) if block
|
26
25
|
|
27
|
-
|
28
|
-
@typelizer_interface ||= Interface.new(serializer: self)
|
26
|
+
@config_layer
|
29
27
|
end
|
30
28
|
|
31
29
|
# save association of serializer to model
|
data/lib/typelizer/generator.rb
CHANGED
@@ -6,24 +6,26 @@ module Typelizer
|
|
6
6
|
new.call(**args)
|
7
7
|
end
|
8
8
|
|
9
|
-
def initialize(config = Typelizer::Config)
|
10
|
-
@config = config
|
11
|
-
@writer = Writer.new
|
12
|
-
end
|
13
|
-
|
14
|
-
attr_reader :config, :writer
|
15
|
-
|
16
9
|
def call(force: false)
|
17
|
-
return unless Typelizer.enabled?
|
10
|
+
return [] unless Typelizer.enabled?
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
found_interfaces
|
22
|
-
end
|
12
|
+
# plugin scan per run cache
|
13
|
+
@scan_plugin_cache = {}
|
23
14
|
|
24
|
-
def interfaces
|
25
15
|
read_serializers
|
26
|
-
target_serializers
|
16
|
+
serializers = target_serializers
|
17
|
+
|
18
|
+
# For each writer, build a dedicated WriterContext. The context holds that writer's
|
19
|
+
# configuration and resolves the effective Config for every Interface (per serializer)
|
20
|
+
# by merging global, writer, and per-serializer (DSL) overrides
|
21
|
+
Typelizer.configuration.writers.each do |writer_name, writer_config|
|
22
|
+
context = WriterContext.new(writer_name: writer_name)
|
23
|
+
interfaces = serializers.map { |klass| context.interface_for(klass) }
|
24
|
+
|
25
|
+
Writer.new(writer_config).call(interfaces, force: force)
|
26
|
+
end
|
27
|
+
|
28
|
+
serializers
|
27
29
|
end
|
28
30
|
|
29
31
|
private
|
@@ -44,9 +46,10 @@ module Typelizer
|
|
44
46
|
files ||= Typelizer.dirs.flat_map { |dir| Dir["#{dir}/**/*.rb"] }
|
45
47
|
files.each do |file|
|
46
48
|
trace = TracePoint.new(:call) do |tp|
|
47
|
-
next unless tp.self.is_a?(Class) && tp.self.respond_to?(:
|
49
|
+
next unless tp.self.is_a?(Class) && tp.self.respond_to?(:typelizer_config)
|
48
50
|
|
49
|
-
serializer_plugin = tp.self
|
51
|
+
serializer_plugin = build_scan_plugin_for(tp.self)
|
52
|
+
next unless serializer_plugin
|
50
53
|
|
51
54
|
if tp.callee_id.in?(serializer_plugin.methods_to_typelize)
|
52
55
|
type, attrs = tp.self.keyless_type
|
@@ -61,5 +64,22 @@ module Typelizer
|
|
61
64
|
trace.disable
|
62
65
|
end
|
63
66
|
end
|
67
|
+
|
68
|
+
# Builds a minimal plugin instance used only during scan time for TracePoint
|
69
|
+
def build_scan_plugin_for(serializer_klass)
|
70
|
+
return @scan_plugin_cache[serializer_klass] if @scan_plugin_cache&.key?(serializer_klass)
|
71
|
+
|
72
|
+
base = Typelizer.configuration.writer_config(:default)
|
73
|
+
local_configuration = serializer_klass.typelizer_config.to_h.slice(:serializer_plugin, :plugin_configs)
|
74
|
+
cfg = base.with_overrides(**local_configuration)
|
75
|
+
|
76
|
+
@scan_plugin_cache[serializer_klass] = cfg.serializer_plugin.new(
|
77
|
+
serializer: serializer_klass,
|
78
|
+
config: cfg,
|
79
|
+
context: Typelizer::ScanContext
|
80
|
+
)
|
81
|
+
rescue NameError
|
82
|
+
nil
|
83
|
+
end
|
64
84
|
end
|
65
85
|
end
|
data/lib/typelizer/interface.rb
CHANGED
@@ -1,14 +1,22 @@
|
|
1
1
|
module Typelizer
|
2
2
|
class Interface
|
3
|
-
attr_reader :serializer, :
|
3
|
+
attr_reader :serializer, :context
|
4
|
+
|
5
|
+
def initialize(serializer:, context:)
|
6
|
+
@serializer = serializer
|
7
|
+
@context = context
|
8
|
+
end
|
4
9
|
|
5
10
|
def config
|
6
|
-
serializer
|
11
|
+
context.config_for(serializer)
|
7
12
|
end
|
8
13
|
|
9
|
-
def
|
10
|
-
@
|
11
|
-
|
14
|
+
def serializer_plugin
|
15
|
+
@serializer_plugin ||= config.serializer_plugin.new(
|
16
|
+
serializer: serializer,
|
17
|
+
config: config,
|
18
|
+
context: context
|
19
|
+
)
|
12
20
|
end
|
13
21
|
|
14
22
|
def inline?
|
@@ -69,12 +77,14 @@ module Typelizer
|
|
69
77
|
|
70
78
|
def parent_interface
|
71
79
|
return if config.inheritance_strategy == :none
|
72
|
-
return unless serializer.superclass.respond_to?(:typelizer_interface)
|
73
80
|
|
74
|
-
|
75
|
-
return
|
81
|
+
parent_class = serializer.superclass
|
82
|
+
return unless parent_class.respond_to?(:typelizer_config)
|
83
|
+
|
84
|
+
parent_interface = context.interface_for(parent_class)
|
85
|
+
return if parent_interface.empty?
|
76
86
|
|
77
|
-
|
87
|
+
parent_interface
|
78
88
|
end
|
79
89
|
|
80
90
|
def imports
|
@@ -140,8 +150,12 @@ module Typelizer
|
|
140
150
|
def model_class
|
141
151
|
return serializer._typelizer_model_name if serializer.respond_to?(:_typelizer_model_name)
|
142
152
|
|
143
|
-
config
|
144
|
-
|
153
|
+
# Execute the `serializer_model_mapper` lambda in the context of the `config` object
|
154
|
+
# This giving a possibility to access other lambdas, for example, `serializer_name_mapper`
|
155
|
+
config.instance_exec(serializer, &config.serializer_model_mapper)
|
156
|
+
rescue NameError => e
|
157
|
+
Typelizer.logger.debug("model_mapper failed for serializer #{serializer.name}: #{e.class}: #{e.message}")
|
158
|
+
|
145
159
|
nil
|
146
160
|
end
|
147
161
|
|
@@ -9,33 +9,61 @@ module Typelizer
|
|
9
9
|
attr_reader :model_class, :config
|
10
10
|
|
11
11
|
def infer_types(prop)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
12
|
+
infer_types_for_association(prop) ||
|
13
|
+
infer_types_for_column(prop) ||
|
14
|
+
infer_types_for_attribute(prop)
|
15
|
+
|
16
|
+
prop
|
17
|
+
end
|
18
|
+
|
19
|
+
def comment_for(prop)
|
20
|
+
column = model_class&.columns_hash&.dig(prop.column_name.to_s)
|
21
|
+
return nil unless column
|
22
|
+
|
23
|
+
prop.comment = column.comment
|
24
|
+
end
|
25
|
+
|
26
|
+
def enum_for(prop)
|
27
|
+
return unless model_class&.defined_enums&.key?(prop.column_name.to_s)
|
28
|
+
|
29
|
+
prop.enum = model_class.defined_enums[prop.column_name.to_s].keys
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def infer_types_for_association(prop)
|
35
|
+
association = model_class&.reflect_on_association(prop.column_name.to_sym)
|
36
|
+
return nil unless association
|
37
|
+
|
38
|
+
case association.macro
|
39
|
+
when :belongs_to
|
40
|
+
foreign_key = association.foreign_key
|
41
|
+
column = model_class&.columns_hash&.dig(foreign_key.to_s)
|
42
|
+
if config.associations_strategy == :database
|
43
|
+
prop.nullable = column.null if column
|
44
|
+
elsif config.associations_strategy == :active_record
|
45
|
+
prop.nullable = association.options[:optional] === true || association.options[:required] === false
|
46
|
+
else
|
47
|
+
raise "Unknown associations strategy: #{config.associations_strategy}"
|
48
|
+
end
|
49
|
+
when :has_one
|
50
|
+
if config.associations_strategy == :database
|
51
|
+
prop.nullable = true
|
52
|
+
elsif config.associations_strategy == :active_record
|
53
|
+
prop.nullable = !association.options[:required]
|
54
|
+
else
|
55
|
+
raise "Unknown associations strategy: #{config.associations_strategy}"
|
32
56
|
end
|
33
|
-
return prop
|
34
57
|
end
|
35
58
|
|
59
|
+
prop
|
60
|
+
end
|
61
|
+
|
62
|
+
def infer_types_for_column(prop)
|
36
63
|
column = model_class&.columns_hash&.dig(prop.column_name.to_s)
|
37
|
-
return
|
64
|
+
return nil unless column
|
38
65
|
|
66
|
+
column = model_class&.columns_hash&.dig(prop.column_name.to_s)
|
39
67
|
prop.multi = !!column.try(:array)
|
40
68
|
case config.null_strategy
|
41
69
|
when :nullable
|
@@ -57,17 +85,20 @@ module Typelizer
|
|
57
85
|
prop
|
58
86
|
end
|
59
87
|
|
60
|
-
def
|
61
|
-
|
62
|
-
return nil unless column
|
88
|
+
def infer_types_for_attribute(prop)
|
89
|
+
return nil unless model_class.respond_to?(:attribute_types)
|
63
90
|
|
64
|
-
|
65
|
-
|
91
|
+
attribute_type_obj = model_class.attribute_types[prop.column_name.to_s]
|
92
|
+
return nil unless attribute_type_obj
|
66
93
|
|
67
|
-
|
68
|
-
|
94
|
+
if attribute_type_obj.respond_to?(:subtype)
|
95
|
+
prop.type = @config.type_mapping[attribute_type_obj.subtype.type]
|
96
|
+
prop.multi = true
|
97
|
+
elsif attribute_type_obj.respond_to?(:type)
|
98
|
+
prop.type = @config.type_mapping[attribute_type_obj.type]
|
99
|
+
end
|
69
100
|
|
70
|
-
prop
|
101
|
+
prop
|
71
102
|
end
|
72
103
|
end
|
73
104
|
end
|
data/lib/typelizer/property.rb
CHANGED
@@ -26,8 +26,9 @@ module Typelizer
|
|
26
26
|
def fingerprint
|
27
27
|
props = to_h
|
28
28
|
props[:type] = type_name
|
29
|
-
props
|
30
|
-
|
29
|
+
props.each_with_object(+"<#{self.class.name}") do |(k, v), fp|
|
30
|
+
fp << " #{k}=#{v.inspect}" unless v.nil?
|
31
|
+
end << ">"
|
31
32
|
end
|
32
33
|
|
33
34
|
private
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Typelizer
|
4
|
+
# SerializerConfigLayer
|
5
|
+
#
|
6
|
+
# Lightweight, validated container for per-serializer overrides defined via the DSL.
|
7
|
+
#
|
8
|
+
# - Backed by a plain Hash for cheap deep-merge later (see WriterContext).
|
9
|
+
# - Only keys from Config.members are allowed; unknown keys raise NoMethodError.
|
10
|
+
# - Supports flat setters/getters in the DSL (e.g., c.null_strategy = :nullable_and_optional).
|
11
|
+
# - Mutable only via the DSL; #to_h returns a frozen hash to prevent external mutation.
|
12
|
+
#
|
13
|
+
# Rationale: we don't allocate another Config here; this layer is merged on top of
|
14
|
+
# library/global/writer settings when computing the effective config.
|
15
|
+
class SerializerConfigLayer
|
16
|
+
VALID_KEYS = Config.members.to_set
|
17
|
+
|
18
|
+
def initialize(target_hash)
|
19
|
+
@target_hash = target_hash
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_h
|
23
|
+
@target_hash.dup.freeze
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def method_missing(name, *args)
|
29
|
+
name = name.to_s
|
30
|
+
key = name.chomp("=").to_sym
|
31
|
+
|
32
|
+
raise NoMethodError, "Unknown configuration key: '#{key}'" unless VALID_KEYS.include?(key)
|
33
|
+
|
34
|
+
return @target_hash[key] = args.first if name.end_with?("=") && args.length == 1
|
35
|
+
|
36
|
+
return @target_hash[key] if args.empty?
|
37
|
+
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def respond_to_missing?(name, include_private = false)
|
42
|
+
VALID_KEYS.include?(name.to_s.chomp("=").to_sym) || super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -92,13 +92,14 @@ module Typelizer
|
|
92
92
|
)
|
93
93
|
when ::Alba::Association
|
94
94
|
resource = attr.instance_variable_get(:@resource)
|
95
|
+
|
95
96
|
Property.new(
|
96
97
|
name: name,
|
97
|
-
type:
|
98
|
+
type: context.interface_for(resource),
|
98
99
|
optional: false,
|
99
100
|
nullable: false,
|
100
101
|
multi: false, # we override this in typelize_method_transform
|
101
|
-
column_name:
|
102
|
+
column_name: attr.name.is_a?(Symbol) ? attr.name.name : attr.name,
|
102
103
|
**options
|
103
104
|
)
|
104
105
|
when ::Alba::TypedAttribute
|
@@ -18,7 +18,7 @@ module Typelizer
|
|
18
18
|
|
19
19
|
def properties
|
20
20
|
serializer._attributes_data.merge(serializer._reflections).flat_map do |key, association|
|
21
|
-
type = association.options[:serializer] ?
|
21
|
+
type = association.options[:serializer] ? context.interface_for(association.options[:serializer]) : nil
|
22
22
|
adapter = ActiveModelSerializers::Adapter.configured_adapter
|
23
23
|
Property.new(
|
24
24
|
name: adapter.transform_key_casing!(key.to_s, association.options),
|
@@ -2,8 +2,8 @@ module Typelizer
|
|
2
2
|
module SerializerPlugins
|
3
3
|
module Auto
|
4
4
|
class << self
|
5
|
-
def new(serializer:, config:)
|
6
|
-
plugin(serializer).new(serializer: serializer, config: config)
|
5
|
+
def new(serializer:, config:, context:)
|
6
|
+
plugin(serializer).new(serializer: serializer, config: config, context: context)
|
7
7
|
end
|
8
8
|
|
9
9
|
def plugin(serializer)
|
@@ -1,9 +1,10 @@
|
|
1
1
|
module Typelizer
|
2
2
|
module SerializerPlugins
|
3
3
|
class Base
|
4
|
-
def initialize(serializer:, config:)
|
4
|
+
def initialize(serializer:, config:, context:)
|
5
5
|
@serializer = serializer
|
6
6
|
@config = config
|
7
|
+
@context = context
|
7
8
|
end
|
8
9
|
|
9
10
|
def root_key
|
@@ -28,7 +29,7 @@ module Typelizer
|
|
28
29
|
|
29
30
|
private
|
30
31
|
|
31
|
-
attr_reader :serializer, :config
|
32
|
+
attr_reader :serializer, :config, :context
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
@@ -18,9 +18,9 @@ module Typelizer
|
|
18
18
|
attributes
|
19
19
|
.flat_map do |key, options|
|
20
20
|
if options[:association] == :flat
|
21
|
-
|
21
|
+
context.interface_for(options.fetch(:serializer)).properties
|
22
22
|
else
|
23
|
-
type = options[:serializer] ?
|
23
|
+
type = options[:serializer] ? context.interface_for(options[:serializer]) : options[:type]
|
24
24
|
Property.new(
|
25
25
|
name: key,
|
26
26
|
type: type,
|
@@ -48,7 +48,7 @@ module Typelizer
|
|
48
48
|
def association_property(assoc, multi: false)
|
49
49
|
key = assoc.name_str
|
50
50
|
serializer = assoc.descriptor.type
|
51
|
-
type = serializer ?
|
51
|
+
type = serializer ? context.interface_for(serializer) : nil
|
52
52
|
Property.new(
|
53
53
|
name: key,
|
54
54
|
type: type,
|
data/lib/typelizer/version.rb
CHANGED
data/lib/typelizer/writer.rb
CHANGED
@@ -4,27 +4,51 @@ require "fileutils"
|
|
4
4
|
|
5
5
|
module Typelizer
|
6
6
|
class Writer
|
7
|
-
|
7
|
+
class WriterError < StandardError; end
|
8
|
+
|
9
|
+
def initialize(config)
|
8
10
|
@template_cache = {}
|
9
|
-
@config =
|
11
|
+
@config = config
|
10
12
|
end
|
11
13
|
|
12
|
-
attr_reader :config, :template_cache
|
13
|
-
|
14
14
|
def call(interfaces, force:)
|
15
15
|
cleanup_output_dir if force
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
valid_interfaces = interfaces.reject(&:empty?)
|
18
|
+
return [] if valid_interfaces.empty?
|
19
19
|
|
20
|
-
|
21
|
-
|
20
|
+
written_files = []
|
21
|
+
|
22
|
+
begin
|
23
|
+
written_files.concat(valid_interfaces.map { |interface| write_interface(interface) })
|
22
24
|
|
23
|
-
|
25
|
+
written_files << write_index(valid_interfaces)
|
26
|
+
|
27
|
+
cleanup_stale_files(written_files) unless force
|
28
|
+
|
29
|
+
Typelizer.logger.debug("Generated #{written_files.size} TypeScript files in #{config.output_dir}")
|
30
|
+
|
31
|
+
written_files
|
32
|
+
rescue => e
|
33
|
+
# if during the file generations an error appears, we remove generated files
|
34
|
+
cleanup_partial_writes(written_files)
|
35
|
+
raise WriterError, "Failed to write TypeScript files (#{e.class}): #{e.message}"
|
36
|
+
end
|
24
37
|
end
|
25
38
|
|
26
39
|
private
|
27
40
|
|
41
|
+
attr_reader :config, :template_cache
|
42
|
+
|
43
|
+
def cleanup_stale_files(written_files)
|
44
|
+
return unless File.directory?(config.output_dir)
|
45
|
+
|
46
|
+
existing_files = Dir[File.join(config.output_dir, "**/*.ts")]
|
47
|
+
stale_files = existing_files - written_files
|
48
|
+
|
49
|
+
File.delete(*stale_files) unless stale_files.empty?
|
50
|
+
end
|
51
|
+
|
28
52
|
def write_index(interfaces)
|
29
53
|
write_file("index.ts", interfaces.map(&:filename).join) do
|
30
54
|
render_template("index.ts.erb", interfaces: interfaces)
|
@@ -60,5 +84,9 @@ module Typelizer
|
|
60
84
|
def cleanup_output_dir
|
61
85
|
FileUtils.rm_rf(config.output_dir)
|
62
86
|
end
|
87
|
+
|
88
|
+
def cleanup_partial_writes(partial_files)
|
89
|
+
File.delete(*partial_files) unless partial_files.empty?
|
90
|
+
end
|
63
91
|
end
|
64
92
|
end
|
data/lib/typelizer.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "typelizer/version"
|
4
|
-
require_relative "typelizer/config"
|
5
4
|
require_relative "typelizer/property"
|
5
|
+
require_relative "typelizer/model_plugins/auto"
|
6
|
+
require_relative "typelizer/serializer_plugins/auto"
|
7
|
+
|
8
|
+
require_relative "typelizer/config"
|
9
|
+
require_relative "typelizer/configuration"
|
10
|
+
require_relative "typelizer/serializer_config_layer"
|
11
|
+
|
12
|
+
require_relative "typelizer/contexts/writer_context"
|
13
|
+
require_relative "typelizer/contexts/scan_context"
|
6
14
|
require_relative "typelizer/interface"
|
7
15
|
require_relative "typelizer/renderer"
|
8
16
|
require_relative "typelizer/writer"
|
9
17
|
require_relative "typelizer/generator"
|
10
|
-
|
11
18
|
require_relative "typelizer/dsl"
|
12
19
|
|
13
|
-
require_relative "typelizer/serializer_plugins/auto"
|
14
20
|
require_relative "typelizer/serializer_plugins/oj_serializers"
|
15
21
|
require_relative "typelizer/serializer_plugins/alba"
|
16
22
|
require_relative "typelizer/serializer_plugins/ams"
|
@@ -18,30 +24,39 @@ require_relative "typelizer/serializer_plugins/panko"
|
|
18
24
|
|
19
25
|
require_relative "typelizer/model_plugins/active_record"
|
20
26
|
require_relative "typelizer/model_plugins/poro"
|
21
|
-
require_relative "typelizer/model_plugins/auto"
|
22
27
|
|
23
28
|
require_relative "typelizer/railtie" if defined?(Rails)
|
24
29
|
|
25
30
|
require "logger"
|
31
|
+
require "forwardable"
|
26
32
|
|
27
33
|
module Typelizer
|
28
34
|
class << self
|
35
|
+
extend Forwardable
|
36
|
+
|
37
|
+
# readers
|
38
|
+
def_delegators :configuration, :dirs, :reject_class, :listen, :writer
|
39
|
+
|
40
|
+
# writers
|
41
|
+
def_delegators :configuration, :dirs=, :reject_class=, :listen=
|
42
|
+
|
29
43
|
def enabled?
|
30
44
|
return false if ENV["DISABLE_TYPELIZER"] == "true" || ENV["DISABLE_TYPELIZER"] == "1"
|
31
45
|
|
32
46
|
ENV["RAILS_ENV"] == "development" || ENV["RACK_ENV"] == "development" || ENV["DISABLE_TYPELIZER"] == "false"
|
33
47
|
end
|
34
48
|
|
35
|
-
attr_accessor :dirs
|
36
|
-
attr_accessor :reject_class
|
37
49
|
attr_accessor :logger
|
38
|
-
attr_accessor :listen
|
39
50
|
|
40
51
|
# @private
|
41
52
|
attr_reader :base_classes
|
42
53
|
|
54
|
+
def configuration
|
55
|
+
@configuration ||= Configuration.new
|
56
|
+
end
|
57
|
+
|
43
58
|
def configure
|
44
|
-
yield
|
59
|
+
yield configuration
|
45
60
|
end
|
46
61
|
|
47
62
|
private
|
@@ -50,10 +65,7 @@ module Typelizer
|
|
50
65
|
end
|
51
66
|
|
52
67
|
# Set in the Railtie
|
53
|
-
self.dirs = []
|
54
|
-
self.reject_class = ->(serializer:) { false }
|
55
68
|
self.logger = Logger.new($stdout, level: :info)
|
56
|
-
self.listen = nil
|
57
69
|
|
58
70
|
self.base_classes = Set.new
|
59
71
|
end
|