sorbet-baml 0.0.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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +115 -0
- data/Rakefile +8 -0
- data/docs/README.md +67 -0
- data/docs/advanced-usage.md +85 -0
- data/docs/getting-started.md +54 -0
- data/docs/troubleshooting.md +81 -0
- data/docs/type-mapping.md +65 -0
- data/lib/sorbet_baml/converter.rb +46 -0
- data/lib/sorbet_baml/type_mapper.rb +79 -0
- data/lib/sorbet_baml/version.rb +5 -0
- data/lib/sorbet_baml.rb +25 -0
- data/sig/sorbet/baml.rbs +6 -0
- data/sorbet/config +4 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/ast@2.4.3.rbi +585 -0
- data/sorbet/rbi/gems/benchmark@0.4.1.rbi +619 -0
- data/sorbet/rbi/gems/byebug@11.1.3.rbi +37 -0
- data/sorbet/rbi/gems/date@3.4.1.rbi +75 -0
- data/sorbet/rbi/gems/diff-lcs@1.6.2.rbi +1134 -0
- data/sorbet/rbi/gems/erb@5.0.2.rbi +878 -0
- data/sorbet/rbi/gems/erubi@1.13.1.rbi +155 -0
- data/sorbet/rbi/gems/io-console@0.8.1.rbi +9 -0
- data/sorbet/rbi/gems/json@2.13.2.rbi +2087 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.5.rbi +9 -0
- data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +240 -0
- data/sorbet/rbi/gems/logger@1.7.0.rbi +963 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +159 -0
- data/sorbet/rbi/gems/parallel@1.27.0.rbi +291 -0
- data/sorbet/rbi/gems/parser@3.3.9.0.rbi +5535 -0
- data/sorbet/rbi/gems/pp@0.6.2.rbi +368 -0
- data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +477 -0
- data/sorbet/rbi/gems/prism@1.4.0.rbi +41732 -0
- data/sorbet/rbi/gems/psych@5.2.6.rbi +2469 -0
- data/sorbet/rbi/gems/racc@1.8.1.rbi +164 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +403 -0
- data/sorbet/rbi/gems/rake@13.3.0.rbi +3031 -0
- data/sorbet/rbi/gems/rbi@0.3.6.rbi +6893 -0
- data/sorbet/rbi/gems/rbs@3.9.4.rbi +6976 -0
- data/sorbet/rbi/gems/rdoc@6.14.2.rbi +12688 -0
- data/sorbet/rbi/gems/regexp_parser@2.11.2.rbi +3845 -0
- data/sorbet/rbi/gems/reline@0.6.2.rbi +2441 -0
- data/sorbet/rbi/gems/rexml@3.4.1.rbi +5240 -0
- data/sorbet/rbi/gems/rspec-core@3.13.5.rbi +11250 -0
- data/sorbet/rbi/gems/rspec-expectations@3.13.5.rbi +8189 -0
- data/sorbet/rbi/gems/rspec-mocks@3.13.5.rbi +5350 -0
- data/sorbet/rbi/gems/rspec-support@3.13.4.rbi +1630 -0
- data/sorbet/rbi/gems/rspec@3.13.1.rbi +83 -0
- data/sorbet/rbi/gems/rubocop-ast@1.46.0.rbi +7764 -0
- data/sorbet/rbi/gems/rubocop-sorbet@0.10.5.rbi +2386 -0
- data/sorbet/rbi/gems/rubocop@1.79.2.rbi +63321 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
- data/sorbet/rbi/gems/spoom@1.6.3.rbi +6985 -0
- data/sorbet/rbi/gems/stringio@3.1.7.rbi +9 -0
- data/sorbet/rbi/gems/tapioca@0.16.11.rbi +3628 -0
- data/sorbet/rbi/gems/thor@1.4.0.rbi +4399 -0
- data/sorbet/rbi/gems/unicode-display_width@3.1.5.rbi +132 -0
- data/sorbet/rbi/gems/unicode-emoji@4.0.4.rbi +251 -0
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +435 -0
- data/sorbet/rbi/gems/yard@0.9.37.rbi +18379 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- metadata +125 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c97a86bec35a852f1d41ff171d595d2f414c999f0421cb19da7d825bc6af5ea6
|
|
4
|
+
data.tar.gz: bc9c45e02ac421d272572b4383c10d4bdf5d87db445be05998ea67cec97fdadc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c0ab662803d02f440767ea8385705ba0f00b4f54cd48343cee0ea067db2569d0b7845bce54882ea60d9a5e50ef3d6ef3688ed063e8720bedc0c5229a23901b95
|
|
7
|
+
data.tar.gz: e36a8d5726d4ef489e21350827b9b0707382fb4fea29714c0583fb8b58ba7806d10dad9e214e326fc023e3750d2711f59bacd79ffd0f0ce54de7368c032a73de
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vicente Reig Rincon de Arellano
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# sorbet-baml
|
|
2
|
+
|
|
3
|
+
Convert Sorbet type definitions to BAML (Boundary AI Markup Language) for more efficient LLM prompting.
|
|
4
|
+
|
|
5
|
+
## What is this?
|
|
6
|
+
|
|
7
|
+
This gem translates Ruby's Sorbet type definitions (T::Struct, T::Enum) into BAML's concise type definition format. BAML uses approximately 60% fewer tokens than JSON Schema while maintaining complete type information.
|
|
8
|
+
|
|
9
|
+
## Why?
|
|
10
|
+
|
|
11
|
+
When working with LLMs, token efficiency matters. Traditional JSON Schema definitions are verbose. BAML provides a more compact representation that LLMs can parse effectively.
|
|
12
|
+
|
|
13
|
+
### Example
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
# Your Sorbet types
|
|
17
|
+
class Address < T::Struct
|
|
18
|
+
const :street, String
|
|
19
|
+
const :city, String
|
|
20
|
+
const :zip, T.nilable(String)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Converts to BAML
|
|
24
|
+
class Address {
|
|
25
|
+
street string
|
|
26
|
+
city string
|
|
27
|
+
zip string?
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Add to your Gemfile:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
gem 'sorbet-baml'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or install directly:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
gem install sorbet-baml
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
require 'sorbet-baml'
|
|
49
|
+
|
|
50
|
+
# Convert a single struct
|
|
51
|
+
baml_definition = SorbetBaml.from_struct(MyStruct)
|
|
52
|
+
|
|
53
|
+
# Convert multiple related structs
|
|
54
|
+
baml_definitions = SorbetBaml.from_structs([User, Address, Order])
|
|
55
|
+
|
|
56
|
+
# With options
|
|
57
|
+
baml = SorbetBaml.from_struct(User,
|
|
58
|
+
include_descriptions: true,
|
|
59
|
+
indent_size: 2
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Current Capabilities
|
|
64
|
+
|
|
65
|
+
✅ **Supported**
|
|
66
|
+
- Basic types (String, Integer, Float, Boolean)
|
|
67
|
+
- T.nilable (optional fields)
|
|
68
|
+
- T::Array
|
|
69
|
+
- Nested T::Struct
|
|
70
|
+
- T::Enum (planned)
|
|
71
|
+
|
|
72
|
+
⚠️ **Limitations**
|
|
73
|
+
- No T::Hash/map support yet
|
|
74
|
+
- No union types (T.any) yet
|
|
75
|
+
- No type aliases yet
|
|
76
|
+
- No runtime validation of generated BAML
|
|
77
|
+
|
|
78
|
+
## Type Mapping
|
|
79
|
+
|
|
80
|
+
| Sorbet | BAML | Status |
|
|
81
|
+
|--------|------|--------|
|
|
82
|
+
| String | string | ✅ |
|
|
83
|
+
| Integer | int | ✅ |
|
|
84
|
+
| Float | float | ✅ |
|
|
85
|
+
| T::Boolean | bool | ✅ |
|
|
86
|
+
| T.nilable(T) | T? | ✅ |
|
|
87
|
+
| T::Array[T] | T[] | ✅ |
|
|
88
|
+
| T::Hash[K,V] | map<K,V> | 🚧 |
|
|
89
|
+
| T.any(T1,T2) | T1 \| T2 | 🚧 |
|
|
90
|
+
| T::Enum | enum Name { ... } | 🚧 |
|
|
91
|
+
|
|
92
|
+
## Development Status
|
|
93
|
+
|
|
94
|
+
This gem is in early development (v0.0.1). The API may change. Use in production at your own risk.
|
|
95
|
+
|
|
96
|
+
### Roadmap
|
|
97
|
+
|
|
98
|
+
- [ ] Complete T::Enum support
|
|
99
|
+
- [ ] Add T::Hash/map conversion
|
|
100
|
+
- [ ] Support union types
|
|
101
|
+
- [ ] Handle circular references
|
|
102
|
+
- [ ] Add type aliases
|
|
103
|
+
- [ ] Preserve field descriptions from comments
|
|
104
|
+
|
|
105
|
+
## Credits
|
|
106
|
+
|
|
107
|
+
This project was inspired by [`sorbet-schema`](https://github.com/maxveldink/sorbet-schema) which provides excellent Sorbet type introspection capabilities. While sorbet-schema focuses on serialization/deserialization, sorbet-baml focuses on generating efficient type definitions for LLM consumption.
|
|
108
|
+
|
|
109
|
+
## Contributing
|
|
110
|
+
|
|
111
|
+
Bug reports and pull requests are welcome at https://github.com/vicentereig/sorbet-baml
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT License. See LICENSE.txt for details.
|
data/Rakefile
ADDED
data/docs/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# sorbet-baml Documentation
|
|
2
|
+
|
|
3
|
+
Developer documentation for the sorbet-baml gem.
|
|
4
|
+
|
|
5
|
+
## For Users
|
|
6
|
+
|
|
7
|
+
If you want to use this gem in your project:
|
|
8
|
+
|
|
9
|
+
1. **[Getting Started](./getting-started.md)** - Installation and basic usage
|
|
10
|
+
2. **[Type Mapping Reference](./type-mapping.md)** - Complete type conversion table
|
|
11
|
+
3. **[Advanced Usage](./advanced-usage.md)** - Complex scenarios and integrations
|
|
12
|
+
4. **[Troubleshooting](./troubleshooting.md)** - Common issues and solutions
|
|
13
|
+
|
|
14
|
+
## For Contributors
|
|
15
|
+
|
|
16
|
+
If you want to contribute to this gem:
|
|
17
|
+
|
|
18
|
+
1. **[Architecture](./architecture.md)** - How the gem is structured
|
|
19
|
+
2. **[Adding Type Support](./adding-types.md)** - How to add new type mappings
|
|
20
|
+
3. **[Testing Guide](./testing.md)** - How to write and run tests
|
|
21
|
+
|
|
22
|
+
## Quick Example
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# Define a Sorbet struct
|
|
26
|
+
class User < T::Struct
|
|
27
|
+
const :name, String
|
|
28
|
+
const :age, Integer
|
|
29
|
+
const :email, T.nilable(String)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Convert to BAML
|
|
33
|
+
require 'sorbet-baml'
|
|
34
|
+
puts SorbetBaml.from_struct(User)
|
|
35
|
+
|
|
36
|
+
# Output:
|
|
37
|
+
# class User {
|
|
38
|
+
# name string
|
|
39
|
+
# age int
|
|
40
|
+
# email string?
|
|
41
|
+
# }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Design Goals
|
|
45
|
+
|
|
46
|
+
1. **Simplicity** - Easy to understand and use
|
|
47
|
+
2. **Accuracy** - Correct type mappings
|
|
48
|
+
3. **Efficiency** - Minimal token usage in output
|
|
49
|
+
4. **Compatibility** - Works with existing Sorbet codebases
|
|
50
|
+
|
|
51
|
+
## What This Is Not
|
|
52
|
+
|
|
53
|
+
- Not a BAML runtime or executor
|
|
54
|
+
- Not a JSON Schema generator (use sorbet-schema for that)
|
|
55
|
+
- Not a Sorbet type checker
|
|
56
|
+
- Not a serialization library
|
|
57
|
+
|
|
58
|
+
## Why BAML?
|
|
59
|
+
|
|
60
|
+
BAML (Boundary AI Markup Language) provides a concise way to define types for LLM consumption. Compared to JSON Schema:
|
|
61
|
+
|
|
62
|
+
- ~60% fewer tokens
|
|
63
|
+
- More readable
|
|
64
|
+
- Better LLM comprehension
|
|
65
|
+
- Simpler syntax
|
|
66
|
+
|
|
67
|
+
Perfect for prompt engineering and structured output generation.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Advanced Usage
|
|
2
|
+
|
|
3
|
+
## Converting Multiple Structs
|
|
4
|
+
|
|
5
|
+
When you have related structs, convert them together to maintain relationships:
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
class Address < T::Struct
|
|
9
|
+
const :street, String
|
|
10
|
+
const :city, String
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class Company < T::Struct
|
|
14
|
+
const :name, String
|
|
15
|
+
const :address, Address
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class User < T::Struct
|
|
19
|
+
const :name, String
|
|
20
|
+
const :company, Company
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Convert all at once
|
|
24
|
+
baml = SorbetBaml.from_structs([Address, Company, User])
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Options
|
|
28
|
+
|
|
29
|
+
### Include Descriptions
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
# Future feature - not yet implemented
|
|
33
|
+
baml = SorbetBaml.from_struct(User,
|
|
34
|
+
include_descriptions: true # Will add @description annotations
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Custom Indentation
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
# Future feature - not yet implemented
|
|
42
|
+
baml = SorbetBaml.from_struct(User,
|
|
43
|
+
indent_size: 4 # Use 4 spaces instead of 2
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Working with Existing BAML Projects
|
|
48
|
+
|
|
49
|
+
If you're already using BAML, you can generate type definitions to include in your `.baml` files:
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
# Generate just the class definition
|
|
53
|
+
definition = SorbetBaml.from_struct(MyStruct)
|
|
54
|
+
|
|
55
|
+
# Write to a BAML file
|
|
56
|
+
File.write("types/my_struct.baml", definition)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Integration with LLM Libraries
|
|
60
|
+
|
|
61
|
+
### With OpenAI
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
require 'openai'
|
|
65
|
+
require 'sorbet-baml'
|
|
66
|
+
|
|
67
|
+
schema = SorbetBaml.from_struct(ResponseFormat)
|
|
68
|
+
|
|
69
|
+
response = client.chat(
|
|
70
|
+
model: "gpt-4",
|
|
71
|
+
messages: [{
|
|
72
|
+
role: "system",
|
|
73
|
+
content: "You must respond with data matching this BAML schema:\n\n#{schema}"
|
|
74
|
+
}, {
|
|
75
|
+
role: "user",
|
|
76
|
+
content: "Generate a sample user"
|
|
77
|
+
}]
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### With DSPy.rb
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
# Coming soon - integration with DSPy.rb for automatic schema generation
|
|
85
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- Ruby 3.2+
|
|
6
|
+
- Sorbet installed in your project
|
|
7
|
+
- Basic familiarity with T::Struct
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
### 1. Define your Sorbet types
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
class User < T::Struct
|
|
15
|
+
const :id, Integer
|
|
16
|
+
const :name, String
|
|
17
|
+
const :email, T.nilable(String)
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### 2. Convert to BAML
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
require 'sorbet-baml'
|
|
25
|
+
|
|
26
|
+
baml = SorbetBaml.from_struct(User)
|
|
27
|
+
puts baml
|
|
28
|
+
# Output:
|
|
29
|
+
# class User {
|
|
30
|
+
# id int
|
|
31
|
+
# name string
|
|
32
|
+
# email string?
|
|
33
|
+
# }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 3. Use with your LLM
|
|
37
|
+
|
|
38
|
+
Include the BAML definition in your prompt:
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
prompt = <<~PROMPT
|
|
42
|
+
Generate sample data matching this schema:
|
|
43
|
+
|
|
44
|
+
#{baml}
|
|
45
|
+
|
|
46
|
+
Return 3 examples.
|
|
47
|
+
PROMPT
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Next Steps
|
|
51
|
+
|
|
52
|
+
- [Type Mapping Reference](./type-mapping.md)
|
|
53
|
+
- [Advanced Usage](./advanced-usage.md)
|
|
54
|
+
- [Troubleshooting](./troubleshooting.md)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## Common Issues
|
|
4
|
+
|
|
5
|
+
### "undefined method `props' for Class"
|
|
6
|
+
|
|
7
|
+
**Problem**: The class you're trying to convert is not a T::Struct.
|
|
8
|
+
|
|
9
|
+
**Solution**: Ensure your class inherits from `T::Struct`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# ❌ Wrong
|
|
13
|
+
class User
|
|
14
|
+
attr_reader :name
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# ✅ Correct
|
|
18
|
+
class User < T::Struct
|
|
19
|
+
const :name, String
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Empty output
|
|
24
|
+
|
|
25
|
+
**Problem**: The struct has no properties defined.
|
|
26
|
+
|
|
27
|
+
**Solution**: Define at least one property using `const` or `prop`:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
class User < T::Struct
|
|
31
|
+
const :name, String # Add properties
|
|
32
|
+
end
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Circular dependency detected
|
|
36
|
+
|
|
37
|
+
**Problem**: Two structs reference each other creating an infinite loop.
|
|
38
|
+
|
|
39
|
+
**Solution**: This is not yet supported. Consider flattening the structure or using a different approach.
|
|
40
|
+
|
|
41
|
+
## Type-Specific Issues
|
|
42
|
+
|
|
43
|
+
### Arrays not converting correctly
|
|
44
|
+
|
|
45
|
+
Ensure you're using the Sorbet array syntax:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# ❌ Wrong
|
|
49
|
+
const :items, Array
|
|
50
|
+
|
|
51
|
+
# ✅ Correct
|
|
52
|
+
const :items, T::Array[String]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Optional fields showing as required
|
|
56
|
+
|
|
57
|
+
Make sure to use `T.nilable`:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# ❌ Wrong - will be required
|
|
61
|
+
const :email, String
|
|
62
|
+
|
|
63
|
+
# ✅ Correct - will be optional
|
|
64
|
+
const :email, T.nilable(String)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Getting Help
|
|
68
|
+
|
|
69
|
+
1. Check the [Type Mapping Reference](./type-mapping.md)
|
|
70
|
+
2. Review the examples in [Getting Started](./getting-started.md)
|
|
71
|
+
3. File an issue at https://github.com/vicentereig/sorbet-baml/issues
|
|
72
|
+
|
|
73
|
+
## Debug Mode
|
|
74
|
+
|
|
75
|
+
To see detailed conversion information:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# Future feature - not yet implemented
|
|
79
|
+
SorbetBaml.debug = true
|
|
80
|
+
baml = SorbetBaml.from_struct(MyStruct)
|
|
81
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Type Mapping Reference
|
|
2
|
+
|
|
3
|
+
Complete mapping between Sorbet types and BAML output.
|
|
4
|
+
|
|
5
|
+
## Basic Types
|
|
6
|
+
|
|
7
|
+
| Sorbet Type | BAML Output | Example |
|
|
8
|
+
|-------------|-------------|---------|
|
|
9
|
+
| `String` | `string` | `name string` |
|
|
10
|
+
| `Integer` | `int` | `age int` |
|
|
11
|
+
| `Float` | `float` | `price float` |
|
|
12
|
+
| `T::Boolean` | `bool` | `active bool` |
|
|
13
|
+
| `NilClass` | `null` | `null` |
|
|
14
|
+
| `Symbol` | `string` | `status string` |
|
|
15
|
+
|
|
16
|
+
## Optional Types
|
|
17
|
+
|
|
18
|
+
| Sorbet Type | BAML Output | Example |
|
|
19
|
+
|-------------|-------------|---------|
|
|
20
|
+
| `T.nilable(String)` | `string?` | `email string?` |
|
|
21
|
+
| `T.nilable(Integer)` | `int?` | `age int?` |
|
|
22
|
+
|
|
23
|
+
## Collection Types
|
|
24
|
+
|
|
25
|
+
| Sorbet Type | BAML Output | Example |
|
|
26
|
+
|-------------|-------------|---------|
|
|
27
|
+
| `T::Array[String]` | `string[]` | `tags string[]` |
|
|
28
|
+
| `T::Array[T::Struct]` | `StructName[]` | `addresses Address[]` |
|
|
29
|
+
|
|
30
|
+
## Nested Structures
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
# Input
|
|
34
|
+
class Address < T::Struct
|
|
35
|
+
const :street, String
|
|
36
|
+
const :city, String
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class User < T::Struct
|
|
40
|
+
const :name, String
|
|
41
|
+
const :address, Address
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Output
|
|
45
|
+
class Address {
|
|
46
|
+
street string
|
|
47
|
+
city string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class User {
|
|
51
|
+
name string
|
|
52
|
+
address Address
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Not Yet Supported
|
|
57
|
+
|
|
58
|
+
These types will be added in future versions:
|
|
59
|
+
|
|
60
|
+
- `T::Hash[K, V]` → `map<K, V>`
|
|
61
|
+
- `T.any(Type1, Type2)` → `Type1 | Type2`
|
|
62
|
+
- `T::Enum` subclasses → `enum Name { ... }`
|
|
63
|
+
- `T.type_alias` → `type Name = ...`
|
|
64
|
+
- `T::Set[T]` → `T[]` (with uniqueness note)
|
|
65
|
+
- `T::Range[T]` → Will need special handling
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module SorbetBaml
|
|
7
|
+
# Main converter class for transforming Sorbet types to BAML
|
|
8
|
+
class Converter
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
sig { params(klass: T.class_of(T::Struct), options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
12
|
+
def self.from_struct(klass, options = {})
|
|
13
|
+
new(options).convert_struct(klass)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
sig { params(klasses: T::Array[T.class_of(T::Struct)], options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
17
|
+
def self.from_structs(klasses, options = {})
|
|
18
|
+
converter = new(options)
|
|
19
|
+
klasses.map { |klass| converter.convert_struct(klass) }.join("\n\n")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
sig { params(options: T::Hash[Symbol, T.untyped]).void }
|
|
23
|
+
def initialize(options = {})
|
|
24
|
+
@options = options
|
|
25
|
+
@indent_size = T.let(options.fetch(:indent_size, 2), Integer)
|
|
26
|
+
@include_descriptions = T.let(options.fetch(:include_descriptions, false), T::Boolean)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
sig { params(klass: T.class_of(T::Struct)).returns(String) }
|
|
30
|
+
def convert_struct(klass)
|
|
31
|
+
props = klass.props
|
|
32
|
+
|
|
33
|
+
class_name = klass.name || klass.to_s
|
|
34
|
+
simple_name = class_name.split('::').last
|
|
35
|
+
lines = ["class #{simple_name} {"]
|
|
36
|
+
|
|
37
|
+
props.each do |name, prop_info|
|
|
38
|
+
baml_type = TypeMapper.map_type(prop_info[:type_object])
|
|
39
|
+
lines << "#{' ' * @indent_size}#{name} #{baml_type}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
lines << "}"
|
|
43
|
+
lines.join("\n")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module SorbetBaml
|
|
7
|
+
# Maps Sorbet type objects to BAML type strings
|
|
8
|
+
class TypeMapper
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
sig { params(type_object: T.untyped).returns(String) }
|
|
12
|
+
def self.map_type(type_object)
|
|
13
|
+
return "string" if type_object.nil?
|
|
14
|
+
|
|
15
|
+
case type_object
|
|
16
|
+
when T::Types::Simple
|
|
17
|
+
map_simple_type(type_object.raw_type)
|
|
18
|
+
when T::Types::TypedArray
|
|
19
|
+
map_array_type(type_object)
|
|
20
|
+
else
|
|
21
|
+
# Check if it's a union type (T.nilable)
|
|
22
|
+
if type_object.respond_to?(:types)
|
|
23
|
+
map_nilable_type(type_object)
|
|
24
|
+
else
|
|
25
|
+
# Fallback for unknown types
|
|
26
|
+
"unknown"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
sig { params(raw_type: T.untyped).returns(String) }
|
|
32
|
+
def self.map_simple_type(raw_type)
|
|
33
|
+
case raw_type.name
|
|
34
|
+
when "String"
|
|
35
|
+
"string"
|
|
36
|
+
when "Integer"
|
|
37
|
+
"int"
|
|
38
|
+
when "Float"
|
|
39
|
+
"float"
|
|
40
|
+
when "TrueClass", "FalseClass"
|
|
41
|
+
"bool"
|
|
42
|
+
when "NilClass"
|
|
43
|
+
"null"
|
|
44
|
+
when "Symbol"
|
|
45
|
+
"string"
|
|
46
|
+
when "Date", "DateTime", "Time"
|
|
47
|
+
"string"
|
|
48
|
+
else
|
|
49
|
+
# Check if it's a T::Struct
|
|
50
|
+
if raw_type < T::Struct
|
|
51
|
+
type_name = raw_type.name || raw_type.to_s
|
|
52
|
+
type_name.split('::').last || "unknown"
|
|
53
|
+
else
|
|
54
|
+
"unknown"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
sig { params(type_object: T.untyped).returns(String) }
|
|
60
|
+
def self.map_nilable_type(type_object)
|
|
61
|
+
# Extract the non-nil type from the union
|
|
62
|
+
types = type_object.types
|
|
63
|
+
non_nil_type = types.find { |t| t.raw_type != NilClass }
|
|
64
|
+
|
|
65
|
+
if non_nil_type
|
|
66
|
+
base_type = map_type(non_nil_type)
|
|
67
|
+
"#{base_type}?"
|
|
68
|
+
else
|
|
69
|
+
"null"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
sig { params(type_object: T.untyped).returns(String) }
|
|
74
|
+
def self.map_array_type(type_object)
|
|
75
|
+
element_type = map_type(type_object.type)
|
|
76
|
+
"#{element_type}[]"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/sorbet_baml.rb
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require_relative "sorbet_baml/version"
|
|
6
|
+
require_relative "sorbet_baml/converter"
|
|
7
|
+
require_relative "sorbet_baml/type_mapper"
|
|
8
|
+
|
|
9
|
+
module SorbetBaml
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
extend T::Sig
|
|
13
|
+
|
|
14
|
+
# Convert a single T::Struct to BAML definition
|
|
15
|
+
sig { params(klass: T.class_of(T::Struct), options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
16
|
+
def self.from_struct(klass, options = {})
|
|
17
|
+
Converter.from_struct(klass, options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Convert multiple T::Structs to BAML definitions
|
|
21
|
+
sig { params(klasses: T::Array[T.class_of(T::Struct)], options: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
22
|
+
def self.from_structs(klasses, options = {})
|
|
23
|
+
Converter.from_structs(klasses, options)
|
|
24
|
+
end
|
|
25
|
+
end
|
data/sig/sorbet/baml.rbs
ADDED
data/sorbet/config
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
**/*.rbi linguist-vendored=true
|