cline-rb 2.2.0 → 2.3.1
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 +29 -0
- data/README.md +2 -1
- data/lib/cline/lenient_schema.rb +116 -0
- data/lib/cline/schema.rb +52 -0
- data/lib/cline/session_message.rb +42 -6
- data/lib/cline/utils/schema.rb +155 -0
- data/lib/cline/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6086330ba4e6dd503fded0fa324c80581b78a5f19bcdbff11d0bf73a33b5b85a
|
|
4
|
+
data.tar.gz: 27c471ceb255adb1e6861af8b5310b687fd0e9967d0d4a2ccf94a9dcce77ab80
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aafe5d90abe5b5f38887ca955762e493a56c2863449319edeaade524752ed328b1720e7f51beb1746acd28060add5313958d58a8774a916d0a45318dd4c8759d
|
|
7
|
+
data.tar.gz: 553cb8f4c177496b170004e3268a02c5c1c6874fdd9e45de62c2339254bb302ceb1f32271987d2a81807fe3a524aa882764c3938fc946733623fa7d703634bc9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
# [v2.3.1](https://github.com/Muriel-Salvan/cline-rb/compare/v2.3.0...v2.3.1) (2026-11-04 18:13:28)
|
|
2
|
+
|
|
3
|
+
## Global changes
|
|
4
|
+
### Patches
|
|
5
|
+
|
|
6
|
+
* [fix(SessionMessage): Fix type of commands from String to Array](https://github.com/Muriel-Salvan/cline-rb/commit/d89408139717eaba0142bcb344a41bc3d135495d)
|
|
7
|
+
|
|
8
|
+
## Changes for SessionMessage
|
|
9
|
+
### Patches
|
|
10
|
+
|
|
11
|
+
* [fix(SessionMessage): Fix type of commands from String to Array](https://github.com/Muriel-Salvan/cline-rb/commit/d89408139717eaba0142bcb344a41bc3d135495d)
|
|
12
|
+
|
|
13
|
+
# [v2.3.0](https://github.com/Muriel-Salvan/cline-rb/compare/v2.2.0...v2.3.0) (2026-11-04 15:21:27)
|
|
14
|
+
|
|
15
|
+
## Global changes
|
|
16
|
+
### Patches
|
|
17
|
+
|
|
18
|
+
* [feat(cline): add LenientSchema for tolerant deserialization of session messages's input](https://github.com/Muriel-Salvan/cline-rb/commit/a033d62989c22876030742c5e4979dc7a02bee16)
|
|
19
|
+
|
|
20
|
+
## Changes for cline
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* [feat(cline): add LenientSchema for tolerant deserialization of session messages's input](https://github.com/Muriel-Salvan/cline-rb/commit/a033d62989c22876030742c5e4979dc7a02bee16)
|
|
24
|
+
|
|
25
|
+
## Changes for schema
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* [feat(cline): add LenientSchema for tolerant deserialization of session messages's input](https://github.com/Muriel-Salvan/cline-rb/commit/a033d62989c22876030742c5e4979dc7a02bee16)
|
|
29
|
+
|
|
1
30
|
# [v2.2.0](https://github.com/Muriel-Salvan/cline-rb/compare/v2.1.1...v2.2.0) (2026-11-03 16:44:07)
|
|
2
31
|
|
|
3
32
|
## Global changes
|
data/README.md
CHANGED
|
@@ -592,7 +592,7 @@ msg.usage #=> Usage struct (cost, tokens)
|
|
|
592
592
|
msg.to_human #=> human-friendly string (limited to 128 chars by default)
|
|
593
593
|
```
|
|
594
594
|
|
|
595
|
-
Contains nested schemas: `ModelInfo`, `Metrics`, `MessageContent`, `ToolUseInput`.
|
|
595
|
+
Contains nested schemas: `ModelInfo`, `Metrics`, `MessageContent`, `ToolUseInput`, `ToolResultContent`.
|
|
596
596
|
|
|
597
597
|
---
|
|
598
598
|
|
|
@@ -877,6 +877,7 @@ Every domain object (settings, providers, sessions, tasks...) extends `Cline::Sc
|
|
|
877
877
|
- **Declarative attributes** — `attribute :my_field, :string` mirrors the JSON schema.
|
|
878
878
|
- **Automatic camelCase↔snake_case mapping** — Cline stores `autoUpdateEnabled`, Ruby gets `auto_update_enabled`.
|
|
879
879
|
- **Extra attribute preservation** — unknown JSON keys are stored in `extra_attributes` and re-serialized back, maintaining round-trip fidelity.
|
|
880
|
+
- **Typed containers & unions** — `Utils::Schema.collection(Type)` models arrays, `Utils::Schema.map(:string)` dynamic-key maps and `Utils::Schema.union(:string, Utils::Schema.collection(Type))` attributes that can be of several types.
|
|
880
881
|
|
|
881
882
|
3 mixin modules handle file persistence — classes include them to "come alive" from disk:
|
|
882
883
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
module Cline
|
|
2
|
+
# Base class for schemas whose data may not respect the expected types on deserialization.
|
|
3
|
+
# This is typically the case for attributes filled by an LLM, whose types are not guaranteed
|
|
4
|
+
# by the Cline CLI (for example a String given where an Array is expected).
|
|
5
|
+
#
|
|
6
|
+
# Contrary to {Schema}, deserialization does not raise exceptions for such wrong data:
|
|
7
|
+
# a warning is emitted, every attribute that can be deserialized properly is set on the instance,
|
|
8
|
+
# and the other attributes are stored in {#wrong_attributes}, keyed by their attribute name.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# input = Cline::SessionMessage::MessageContent::ToolUseInput.of_hash(
|
|
12
|
+
# 'question' => 'Continue?',
|
|
13
|
+
# 'files' => 'not-an-array'
|
|
14
|
+
# )
|
|
15
|
+
# input.question #=> 'Continue?'
|
|
16
|
+
# input.files #=> nil
|
|
17
|
+
# input.wrong_attributes #=> { files: 'not-an-array' }
|
|
18
|
+
class LenientSchema < Schema
|
|
19
|
+
extend Utils::Logger
|
|
20
|
+
|
|
21
|
+
# @!group Public API
|
|
22
|
+
|
|
23
|
+
# The attributes that could not be deserialized, keyed by their attribute name, with their raw value.
|
|
24
|
+
# Only set when at least one attribute could not be deserialized.
|
|
25
|
+
# When the whole value is not a Hash, it is stored under the '_raw' key.
|
|
26
|
+
#
|
|
27
|
+
# @return [Hash{Symbol => Object}, nil] The wrong attributes, or nil if deserialization succeeded
|
|
28
|
+
attr_accessor :wrong_attributes
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
# @!group Internal
|
|
32
|
+
|
|
33
|
+
# Cast an input value to this Schema object.
|
|
34
|
+
# Any value that cannot be cast is turned into an empty instance with {#wrong_attributes} set,
|
|
35
|
+
# instead of raising an exception.
|
|
36
|
+
#
|
|
37
|
+
# @param value [Schema, Hash, nil] The value that could be used to initialize a new instance of this attribute
|
|
38
|
+
# @return [Schema, nil] The corresponding instance, or nil if none
|
|
39
|
+
def cast(value)
|
|
40
|
+
super
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
# TODO: Make this a warn message when logger will be implemented properly
|
|
43
|
+
log_debug "!!! Cline: Unable to cast a #{value.class} into a #{name} (#{e.class}: #{e.message}). Raw value: #{value.to_s[0, 256]}"
|
|
44
|
+
instance = new
|
|
45
|
+
instance.wrong_attributes = { '_raw' => value.to_s }
|
|
46
|
+
instance
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Parse a Hash object and instantiate the proper instance from it.
|
|
50
|
+
# Instead of raising an exception when an attribute cannot be deserialized
|
|
51
|
+
# (wrong type, wrong structure...), this attribute is left unset and its raw value
|
|
52
|
+
# is stored in {#wrong_attributes}, while a warning is emitted.
|
|
53
|
+
# Attributes that can be deserialized properly are still set on the instance.
|
|
54
|
+
#
|
|
55
|
+
# @param hash [Hash] Data
|
|
56
|
+
# @param args [Array] Remaining arguments to be transferred to Shale
|
|
57
|
+
# @param kwargs [Hash] Remaining kwargs to be transferred to Shale
|
|
58
|
+
# @return [LenientSchema] Corresponding instance
|
|
59
|
+
def of_hash(hash, *args, **kwargs)
|
|
60
|
+
complete_hash_mapping
|
|
61
|
+
instance = new
|
|
62
|
+
unless hash.is_a?(Hash)
|
|
63
|
+
# TODO: Make this a warn message when logger will be implemented properly
|
|
64
|
+
log_debug "Cline: Unable to deserialize a #{name} from a #{hash.class}. Raw value: #{hash.to_s[0, 256]}"
|
|
65
|
+
instance.wrong_attributes = { '_raw' => hash.to_s }
|
|
66
|
+
return instance
|
|
67
|
+
end
|
|
68
|
+
mappings = hash_mapping.keys
|
|
69
|
+
wrong_attributes = {}
|
|
70
|
+
hash.each do |key, value|
|
|
71
|
+
mapping = mappings[key]
|
|
72
|
+
if mapping.nil?
|
|
73
|
+
# Keep unknown attributes for round-trip fidelity, as Schema does
|
|
74
|
+
(instance.extra_attributes ||= {})[key] = value
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
begin
|
|
78
|
+
set_attribute_from_hash(instance, mapping, value, *args, **kwargs)
|
|
79
|
+
rescue StandardError => e
|
|
80
|
+
# TODO: Make this a warn message when logger will be implemented properly
|
|
81
|
+
log_debug "Cline: Unable to deserialize attribute #{key.inspect} of #{name} (#{e.class}: #{e.message}). Raw value: #{value.to_s[0, 256]}"
|
|
82
|
+
wrong_attributes[mapping.attribute] = value
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
instance.wrong_attributes = wrong_attributes unless wrong_attributes.empty?
|
|
86
|
+
instance
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
# Set one attribute of an instance from its Hash value, mirroring Shale's own of_hash logic.
|
|
92
|
+
#
|
|
93
|
+
# @param instance [LenientSchema] The instance to set the attribute on
|
|
94
|
+
# @param mapping [Shale::Mapping::Descriptor::Dict] The hash mapping of the attribute
|
|
95
|
+
# @param value [Object] The raw value to deserialize
|
|
96
|
+
# @param args [Array] Remaining arguments to be transferred to Shale
|
|
97
|
+
# @param kwargs [Hash] Remaining kwargs to be transferred to Shale
|
|
98
|
+
# @return [void]
|
|
99
|
+
def set_attribute_from_hash(instance, mapping, value, *args, **kwargs)
|
|
100
|
+
attribute = attributes[mapping.attribute]
|
|
101
|
+
check_scalar_value!(attribute, value)
|
|
102
|
+
casted_value =
|
|
103
|
+
if value.nil?
|
|
104
|
+
nil
|
|
105
|
+
elsif attribute.collection?
|
|
106
|
+
(value.is_a?(Array) ? value : [value]).map do |element|
|
|
107
|
+
element.nil? ? nil : attribute.type.cast(attribute.type.of_hash(element, *args, **kwargs))
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
attribute.type.cast(attribute.type.of_hash(value, *args, **kwargs))
|
|
111
|
+
end
|
|
112
|
+
instance.send(attribute.setter, casted_value)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/cline/schema.rb
CHANGED
|
@@ -10,6 +10,8 @@ module Cline
|
|
|
10
10
|
class Schema < Shale::Mapper
|
|
11
11
|
# @!group Internal
|
|
12
12
|
|
|
13
|
+
include Utils::Logger
|
|
14
|
+
|
|
13
15
|
# @return [Hash] Store all extra values from a JSON parse
|
|
14
16
|
attr_accessor :extra_attributes
|
|
15
17
|
|
|
@@ -23,6 +25,31 @@ module Cline
|
|
|
23
25
|
@snake_attributes.uniq!
|
|
24
26
|
end
|
|
25
27
|
|
|
28
|
+
# Enable strict type checks on deserialization.
|
|
29
|
+
# When enabled, a scalar attribute that receives an Array or a Hash raises a TypeError
|
|
30
|
+
# instead of being silently coerced.
|
|
31
|
+
#
|
|
32
|
+
# @return [void]
|
|
33
|
+
def strict_types!
|
|
34
|
+
@strict_types = true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Are strict type checks enabled on deserialization?
|
|
38
|
+
#
|
|
39
|
+
# @return [Boolean] true if strict type checks are enabled
|
|
40
|
+
def strict_types?
|
|
41
|
+
@strict_types == true
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Check if a raw value can be deserialized by this schema.
|
|
45
|
+
# By default a schema is deserialized from a Hash, or from an instance of itself.
|
|
46
|
+
#
|
|
47
|
+
# @param raw_value [Object] The raw value to check
|
|
48
|
+
# @return [Boolean] true if {of_hash} can deserialize this value
|
|
49
|
+
def accepts_value?(raw_value)
|
|
50
|
+
raw_value.is_a?(self) || raw_value.is_a?(Hash)
|
|
51
|
+
end
|
|
52
|
+
|
|
26
53
|
# Parse a Hash object and instantiate the proper instance from it.
|
|
27
54
|
#
|
|
28
55
|
# @param hash [Hash] Data
|
|
@@ -42,6 +69,7 @@ module Cline
|
|
|
42
69
|
extra_hash[key] = value
|
|
43
70
|
end
|
|
44
71
|
end
|
|
72
|
+
validate_scalar_types!(known_hash) if strict_types?
|
|
45
73
|
# Give Shale the data it knows about, without extra attributes
|
|
46
74
|
instance = super(known_hash, *args, **kwargs)
|
|
47
75
|
instance.extra_attributes = extra_hash unless extra_hash.empty?
|
|
@@ -81,6 +109,30 @@ module Cline
|
|
|
81
109
|
|
|
82
110
|
private
|
|
83
111
|
|
|
112
|
+
# Validate that scalar attributes do not receive a structured value.
|
|
113
|
+
#
|
|
114
|
+
# @param known_hash [Hash] The known attributes to validate
|
|
115
|
+
# @return [void]
|
|
116
|
+
def validate_scalar_types!(known_hash)
|
|
117
|
+
known_hash.each do |key, value|
|
|
118
|
+
attribute = attributes[hash_mapping.keys[key].attribute]
|
|
119
|
+
check_scalar_value!(attribute, value) if attribute
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check that a value is structurally compatible with an attribute.
|
|
124
|
+
# A scalar attribute should not receive an Array or a Hash.
|
|
125
|
+
#
|
|
126
|
+
# @param attribute [Shale::Attribute] The attribute to check
|
|
127
|
+
# @param value [Object] The raw value to check
|
|
128
|
+
# @return [void]
|
|
129
|
+
def check_scalar_value!(attribute, value)
|
|
130
|
+
return if value.nil? || attribute.collection? || attribute.type < Shale::Type::Complex
|
|
131
|
+
return unless value.is_a?(Array) || value.is_a?(Hash)
|
|
132
|
+
|
|
133
|
+
raise TypeError, "Attribute #{attribute.name.inspect} of #{name} expects a scalar value but got a #{value.class}"
|
|
134
|
+
end
|
|
135
|
+
|
|
84
136
|
# Complete the hash mapping to include camelCase to snake_case as the defaults.
|
|
85
137
|
# Do it only once.
|
|
86
138
|
def complete_hash_mapping
|
|
@@ -46,13 +46,18 @@ module Cline
|
|
|
46
46
|
# @!group Public API
|
|
47
47
|
|
|
48
48
|
# An input used for tool use content.
|
|
49
|
-
|
|
49
|
+
# Its values are filled by the LLM, whose types are not guaranteed by the Cline CLI:
|
|
50
|
+
# deserialization is therefore lenient (wrong values do not raise but are stored in {#wrong_attributes}).
|
|
51
|
+
class ToolUseInput < LenientSchema
|
|
50
52
|
# @!group Public API
|
|
51
53
|
|
|
52
54
|
# A file to be read by the read_files tool
|
|
55
|
+
# The path is expected to be a String: a wrong type (e.g. an Array) is reported as a deserialization error.
|
|
53
56
|
class FileInput < Schema
|
|
54
57
|
# @!group Public API
|
|
55
58
|
|
|
59
|
+
strict_types!
|
|
60
|
+
|
|
56
61
|
# @return [String] File path
|
|
57
62
|
attribute :path, :string
|
|
58
63
|
end
|
|
@@ -72,8 +77,8 @@ module Cline
|
|
|
72
77
|
# @return [String, nil] Old file content (for editor tool)
|
|
73
78
|
attribute :old_text, :string
|
|
74
79
|
|
|
75
|
-
# @return [String
|
|
76
|
-
attribute :commands, :string
|
|
80
|
+
# @return [Array<String>, nil] Shell commands to run (for run_commands tool)
|
|
81
|
+
attribute :commands, Utils::Schema.collection(:string)
|
|
77
82
|
|
|
78
83
|
# @return [Array<FileInput>, nil] Files to read (for read_files tool)
|
|
79
84
|
attribute :files, Utils::Schema.collection(FileInput)
|
|
@@ -81,6 +86,27 @@ module Cline
|
|
|
81
86
|
cline_snake_attributes :new_text, :old_text
|
|
82
87
|
end
|
|
83
88
|
|
|
89
|
+
# A content entry of a tool result that is not a plain String.
|
|
90
|
+
class ToolResultContent < Schema
|
|
91
|
+
# @!group Public API
|
|
92
|
+
|
|
93
|
+
# @return [String, nil] Query or command executed by the tool
|
|
94
|
+
attribute :query, :string
|
|
95
|
+
|
|
96
|
+
# @return [String, nil] Result of the query or command
|
|
97
|
+
attribute :result, :string
|
|
98
|
+
|
|
99
|
+
# @return [Boolean, nil] Whether the query or command succeeded
|
|
100
|
+
attribute :success, :boolean
|
|
101
|
+
|
|
102
|
+
# Get a human-readable representation of this tool result.
|
|
103
|
+
#
|
|
104
|
+
# @return [String] The result of the tool, or its query if there is no result
|
|
105
|
+
def to_s
|
|
106
|
+
result || query || ''
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
84
110
|
# @return [String] Content block type (text, tool_use, tool_result)
|
|
85
111
|
attribute :type, :string
|
|
86
112
|
|
|
@@ -102,8 +128,8 @@ module Cline
|
|
|
102
128
|
# @return [String, nil] Tool use identifier this result corresponds to (for type "tool_result")
|
|
103
129
|
attribute :tool_use_id, :string
|
|
104
130
|
|
|
105
|
-
# @return [String, nil] Content of the tool result (for type "tool_result")
|
|
106
|
-
attribute :content, :string
|
|
131
|
+
# @return [String, Array<ToolResultContent>, nil] Content of the tool result (for type "tool_result")
|
|
132
|
+
attribute :content, Utils::Schema.union(:string, Utils::Schema.collection(ToolResultContent))
|
|
107
133
|
|
|
108
134
|
cline_snake_attributes :tool_use_id
|
|
109
135
|
end
|
|
@@ -169,7 +195,7 @@ module Cline
|
|
|
169
195
|
"Assistant: #{first_text}"
|
|
170
196
|
end
|
|
171
197
|
when 'tool'
|
|
172
|
-
"Tool result: #{
|
|
198
|
+
"Tool result: #{human_tool_result}"
|
|
173
199
|
else
|
|
174
200
|
to_s
|
|
175
201
|
end
|
|
@@ -202,5 +228,15 @@ module Cline
|
|
|
202
228
|
def first_text
|
|
203
229
|
content.find { |c| c.type == 'text' }&.text.to_s.gsub("\n", ' ')
|
|
204
230
|
end
|
|
231
|
+
|
|
232
|
+
# Get a human representation of the tool result content of this message.
|
|
233
|
+
#
|
|
234
|
+
# @return [String] The tool result content, as a String
|
|
235
|
+
def human_tool_result
|
|
236
|
+
tool_result = content.find { |c| c.type == 'tool_result' }&.content
|
|
237
|
+
return tool_result.to_s if tool_result.nil? || tool_result.is_a?(String)
|
|
238
|
+
|
|
239
|
+
Array(tool_result).join(' ')
|
|
240
|
+
end
|
|
205
241
|
end
|
|
206
242
|
end
|
data/lib/cline/utils/schema.rb
CHANGED
|
@@ -74,6 +74,14 @@ module Cline
|
|
|
74
74
|
subclass.value_type = value_type
|
|
75
75
|
end
|
|
76
76
|
|
|
77
|
+
# Check if a raw value can be deserialized by this map.
|
|
78
|
+
#
|
|
79
|
+
# @param raw_value [Object] The raw value to check
|
|
80
|
+
# @return [Boolean] true if this map can deserialize this value
|
|
81
|
+
def accepts_value?(raw_value)
|
|
82
|
+
raw_value.is_a?(self) || raw_value.is_a?(Hash)
|
|
83
|
+
end
|
|
84
|
+
|
|
77
85
|
# Parse a Hash object and instantiate the proper instance from it.
|
|
78
86
|
#
|
|
79
87
|
# @param hash [Hash] Data
|
|
@@ -233,6 +241,14 @@ module Cline
|
|
|
233
241
|
subclass.value_type = value_type
|
|
234
242
|
end
|
|
235
243
|
|
|
244
|
+
# Check if a raw value can be deserialized by this collection.
|
|
245
|
+
#
|
|
246
|
+
# @param raw_value [Object] The raw value to check
|
|
247
|
+
# @return [Boolean] true if this collection can deserialize this value
|
|
248
|
+
def accepts_value?(raw_value)
|
|
249
|
+
raw_value.is_a?(self) || raw_value.is_a?(Array)
|
|
250
|
+
end
|
|
251
|
+
|
|
236
252
|
# Parse a Ruby object and instantiate the proper instance from it.
|
|
237
253
|
#
|
|
238
254
|
# @param hash [Object] Data
|
|
@@ -285,6 +301,145 @@ module Cline
|
|
|
285
301
|
end
|
|
286
302
|
Schema.const_get(external_name)
|
|
287
303
|
end
|
|
304
|
+
|
|
305
|
+
# Provide a class that can be used by Shale for attributes that can be of several types, like this:
|
|
306
|
+
# "content": "Some text" # A String
|
|
307
|
+
# "content": [{ "query": "ls" }, ...] # An Array of structures
|
|
308
|
+
# The first type accepting a value is used to deserialize / serialize it.
|
|
309
|
+
#
|
|
310
|
+
# @param shale_types [Array<Symbol, Class>] The Shale types this attribute can be (at least 2)
|
|
311
|
+
# @return [Class] Class that can be used in an attribute declaration
|
|
312
|
+
def self.union(*shale_types)
|
|
313
|
+
Union.of(*shale_types)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Handle union types: attributes that can be of several types.
|
|
317
|
+
# Used by {Cline::Utils::Schema.union}.
|
|
318
|
+
class Union
|
|
319
|
+
# @!group Internal
|
|
320
|
+
|
|
321
|
+
# Ruby classes that can be deserialized by each scalar Shale type.
|
|
322
|
+
# Shale types not listed here are considered to accept any value.
|
|
323
|
+
SCALAR_VALUE_CLASSES = {
|
|
324
|
+
Shale::Type::String => String,
|
|
325
|
+
Shale::Type::Integer => Integer,
|
|
326
|
+
Shale::Type::Float => Float,
|
|
327
|
+
Shale::Type::Boolean => [TrueClass, FalseClass],
|
|
328
|
+
Shale::Type::Date => Date,
|
|
329
|
+
Shale::Type::Time => Time,
|
|
330
|
+
Shale::Type::Decimal => BigDecimal
|
|
331
|
+
}.freeze
|
|
332
|
+
|
|
333
|
+
class << self
|
|
334
|
+
# Build (or retrieve) the type class for a union of the given types.
|
|
335
|
+
#
|
|
336
|
+
# @param shale_types [Array<Symbol, Class>] The Shale types this union can be (at least 2)
|
|
337
|
+
# @return [Class] Class that can be used in an attribute declaration
|
|
338
|
+
def of(*shale_types)
|
|
339
|
+
raise ArgumentError, "A union type needs at least 2 types, got #{shale_types.size}" if shale_types.size < 2
|
|
340
|
+
|
|
341
|
+
shale_types = shale_types.map { |shale_type| shale_type.is_a?(Symbol) ? Shale::Type.lookup(shale_type) : shale_type }
|
|
342
|
+
# Give a name to this class because Shale needs it to reference it in its accessors.
|
|
343
|
+
# That also allows us to not redefine the same class several times.
|
|
344
|
+
external_name = :"UnionOf#{shale_types.map { |shale_type| shale_type.to_s.gsub(':', '') }.join('Or')}"
|
|
345
|
+
unless Cline::Utils::Schema.const_defined?(external_name)
|
|
346
|
+
union_class = new_union_class
|
|
347
|
+
union_class.union_types = shale_types
|
|
348
|
+
union_class.external_name = external_name
|
|
349
|
+
Cline::Utils::Schema.const_set(external_name, union_class)
|
|
350
|
+
end
|
|
351
|
+
Cline::Utils::Schema.const_get(external_name)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Check if a raw value can be deserialized by a given Shale type.
|
|
355
|
+
# Shale types that don't handle accepted values are considered as scalar types.
|
|
356
|
+
#
|
|
357
|
+
# @param shale_type [Class] The Shale type to check
|
|
358
|
+
# @param raw_value [Object] The raw value to check
|
|
359
|
+
# @return [Boolean] true if the Shale type can deserialize this value
|
|
360
|
+
def accepts_value?(shale_type, raw_value)
|
|
361
|
+
if shale_type.respond_to?(:accepts_value?)
|
|
362
|
+
shale_type.accepts_value?(raw_value)
|
|
363
|
+
else
|
|
364
|
+
raw_value.is_a?(SCALAR_VALUE_CLASSES.fetch(shale_type, Object))
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
private
|
|
369
|
+
|
|
370
|
+
# Create the class implementing a union type.
|
|
371
|
+
#
|
|
372
|
+
# @return [Class] The new union type class
|
|
373
|
+
def new_union_class
|
|
374
|
+
Class.new(Cline::Schema) do
|
|
375
|
+
class << self
|
|
376
|
+
attr_accessor :union_types, :external_name
|
|
377
|
+
|
|
378
|
+
# Check if a raw value can be deserialized by one of this union's types.
|
|
379
|
+
#
|
|
380
|
+
# @param raw_value [Object] The raw value to check
|
|
381
|
+
# @return [Boolean] true if one of this union's types accepts this value
|
|
382
|
+
def accepts_value?(raw_value)
|
|
383
|
+
raw_value.is_a?(self) ||
|
|
384
|
+
union_types.any? { |union_type| Cline::Utils::Schema::Union.accepts_value?(union_type, raw_value) }
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Parse a value and instantiate the proper instance from it.
|
|
388
|
+
#
|
|
389
|
+
# @param value [Object] Data
|
|
390
|
+
# @param args [Array] Remaining arguments to be transferred to Shale
|
|
391
|
+
# @param kwargs [Hash] Remaining kwargs to be transferred to Shale
|
|
392
|
+
# @return [Object, nil] Corresponding instance, or nil if value is nil
|
|
393
|
+
def of_hash(value, *args, **kwargs)
|
|
394
|
+
return nil if value.nil?
|
|
395
|
+
|
|
396
|
+
union_type_for(value).of_hash(value, *args, **kwargs)
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Get a Ruby object from an instance.
|
|
400
|
+
#
|
|
401
|
+
# @param value [Object] Object to serialize
|
|
402
|
+
# @param args [Array] Remaining arguments to be transferred to Shale
|
|
403
|
+
# @param kwargs [Hash] Remaining kwargs to be transferred to Shale
|
|
404
|
+
# @return [Object, nil] Corresponding Ruby object, or nil if value is nil
|
|
405
|
+
def as_hash(value, *args, **kwargs)
|
|
406
|
+
return nil if value.nil?
|
|
407
|
+
|
|
408
|
+
union_type_for(value).as_hash(value, *args, **kwargs)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Cast an input value to the type matching it.
|
|
412
|
+
#
|
|
413
|
+
# @param value [Object, nil] The value to cast
|
|
414
|
+
# @return [Object, nil] The corresponding instance, or nil if none
|
|
415
|
+
def cast(value)
|
|
416
|
+
return nil if value.nil?
|
|
417
|
+
|
|
418
|
+
union_type_for(value).cast(value)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Return the class name
|
|
422
|
+
#
|
|
423
|
+
# @return [String] The class name
|
|
424
|
+
def to_s
|
|
425
|
+
"::Cline::Utils::Schema::#{@external_name}"
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
private
|
|
429
|
+
|
|
430
|
+
# Get the type that can deserialize / serialize a given value.
|
|
431
|
+
#
|
|
432
|
+
# @param value [Object] The value to find the type of
|
|
433
|
+
# @return [Class] The corresponding type
|
|
434
|
+
def union_type_for(value)
|
|
435
|
+
union_types.find { |union_type| Cline::Utils::Schema::Union.accepts_value?(union_type, value) } ||
|
|
436
|
+
raise("Unable to cast a #{value.class} into #{name}")
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
288
443
|
end
|
|
289
444
|
end
|
|
290
445
|
end
|
data/lib/cline/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cline-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Muriel Salvan
|
|
@@ -187,6 +187,7 @@ files:
|
|
|
187
187
|
- lib/cline/global_state/models.rb
|
|
188
188
|
- lib/cline/global_state/toggles.rb
|
|
189
189
|
- lib/cline/global_state/workspace.rb
|
|
190
|
+
- lib/cline/lenient_schema.rb
|
|
190
191
|
- lib/cline/log.rb
|
|
191
192
|
- lib/cline/logs.rb
|
|
192
193
|
- lib/cline/mcp_settings.rb
|