bubblezone 0.1.0-x86-linux-musl
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/LICENSE.txt +21 -0
- data/README.md +370 -0
- data/bubblezone.gemspec +39 -0
- data/ext/bubblezone/extconf.rb +65 -0
- data/ext/bubblezone/extension.c +107 -0
- data/ext/bubblezone/extension.h +36 -0
- data/ext/bubblezone/manager.c +127 -0
- data/ext/bubblezone/zone_info.c +100 -0
- data/go/bubblezone.go +369 -0
- data/go/build/linux_386/libbubblezone.a +0 -0
- data/go/build/linux_386/libbubblezone.h +114 -0
- data/go/go.mod +28 -0
- data/go/go.sum +47 -0
- data/lib/bubblezone/3.2/bubblezone.so +0 -0
- data/lib/bubblezone/3.3/bubblezone.so +0 -0
- data/lib/bubblezone/3.4/bubblezone.so +0 -0
- data/lib/bubblezone/4.0/bubblezone.so +0 -0
- data/lib/bubblezone/manager.rb +56 -0
- data/lib/bubblezone/version.rb +5 -0
- data/lib/bubblezone/zone_info.rb +25 -0
- data/lib/bubblezone.rb +59 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 85a0fab73e612b6ccd5423e61f5ec8e532ea4a0ccb140fb5451aba4b276f4293
|
|
4
|
+
data.tar.gz: d1dea9fef2788480cf9d04742d476fcab63b558d33d167e818f859a9be6c533a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: de9c32d6029954e541543d817707524e791cba6feec672c343aae966ac9770a1d4e5188d1d2856495f1d3caf87e1a8a44c6de1d20009013fc9da4870ec680c00
|
|
7
|
+
data.tar.gz: d69e60cfd2c7b02805c172a88a6acf290fcd04e9bb1141e8bb11277e28ba9852bdcc0d2f5bfa60548ed0aed1f33d1fd4ad4fd882a0f592f57d0408822e0644a1
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Marco Roth
|
|
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,370 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1>Bubblezone for Ruby</h1>
|
|
3
|
+
<h4>Helper utility for <a href="https://github.com/marcoroth/bubbletea-ruby">Bubble Tea</a>, allowing easy mouse event tracking in terminal applications.</h4>
|
|
4
|
+
|
|
5
|
+
<p>
|
|
6
|
+
<a href="https://rubygems.org/gems/bubblezone"><img alt="Gem Version" src="https://img.shields.io/gem/v/bubblezone"></a>
|
|
7
|
+
<a href="https://github.com/marcoroth/bubblezone-ruby/blob/main/LICENSE.txt"><img alt="License" src="https://img.shields.io/github/license/marcoroth/bubblezone-ruby"></a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p>Ruby bindings for <a href="https://github.com/lrstanley/bubblezone">lrstanley/bubblezone</a>.<br/>Track clickable regions in terminal UIs. Built for use with <a href="https://github.com/marcoroth/bubbletea-ruby">Bubble Tea</a> and <a href="https://github.com/marcoroth/lipgloss-ruby">Lipgloss</a>.</p>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
**Add to your Gemfile:**
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "bubblezone"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Or install directly:**
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
gem install bubblezone
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Basic Zone Marking
|
|
30
|
+
|
|
31
|
+
**Initialize the global zone manager:**
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
require "bubblezone"
|
|
35
|
+
|
|
36
|
+
Bubblezone.new_global
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Mark a region with an ID:**
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
button = Bubblezone.mark("my_button", "Click Me")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Build your layout and scan to register zones:**
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
layout = "Header\n#{button}\nFooter"
|
|
49
|
+
output = Bubblezone.scan(layout)
|
|
50
|
+
puts output
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Output:**
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Header
|
|
57
|
+
Click Me
|
|
58
|
+
Footer
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Getting Zone Information
|
|
62
|
+
|
|
63
|
+
**Get zone info by ID:**
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
zone = Bubblezone.get("my_button")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Check zone bounds:**
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
if zone
|
|
73
|
+
puts "Zone bounds: (#{zone.start_x}, #{zone.start_y}) to (#{zone.end_x}, #{zone.end_y})"
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Checking Mouse Coordinates
|
|
78
|
+
|
|
79
|
+
**Get coordinates from mouse event:**
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
x, y = message.x, message.y
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Check if coordinates are within a zone:**
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
zone = Bubblezone.get("my_button")
|
|
89
|
+
if zone&.in_bounds?(x, y)
|
|
90
|
+
puts "Button clicked!"
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Iterating Over Zones
|
|
95
|
+
|
|
96
|
+
**Iterate over all zones containing the coordinates:**
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
Bubblezone.each_in_bounds(x, y) do |id, zone|
|
|
100
|
+
puts "Hit zone: #{id}"
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**Check if any zone contains the coordinates:**
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
if Bubblezone.any_in_bounds?(x, y)
|
|
108
|
+
puts "Something was clicked"
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Get the first matching zone:**
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
result = Bubblezone.find_in_bounds(x, y)
|
|
116
|
+
if result
|
|
117
|
+
id, zone = result
|
|
118
|
+
puts "First hit: #{id}"
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Zone Prefixes
|
|
123
|
+
|
|
124
|
+
**Prevent ID conflicts between components:**
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
class MyComponent
|
|
128
|
+
def initialize
|
|
129
|
+
@prefix = Bubblezone.new_prefix
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def view
|
|
133
|
+
items = ["Apple", "Banana", "Cherry"]
|
|
134
|
+
items.map.with_index do |item, i|
|
|
135
|
+
Bubblezone.mark("#{@prefix}#{i}", item)
|
|
136
|
+
end.join("\n")
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Manager Instances
|
|
142
|
+
|
|
143
|
+
**Create a dedicated manager:**
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
manager = Bubblezone::Manager.new
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Use the same API as the global manager:**
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
marked = manager.mark("zone_id", "Content")
|
|
153
|
+
output = manager.scan(marked)
|
|
154
|
+
zone = manager.get("zone_id")
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Iterate zones:**
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
manager.each_in_bounds(x, y) { |id, zone| ... }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Clean up when done:**
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
manager.close
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Integration with Bubbletea
|
|
170
|
+
|
|
171
|
+
**Handle mouse clicks in a Bubbletea model:**
|
|
172
|
+
|
|
173
|
+
```ruby
|
|
174
|
+
require "bubbletea"
|
|
175
|
+
require "bubblezone"
|
|
176
|
+
|
|
177
|
+
Bubblezone.new_global
|
|
178
|
+
|
|
179
|
+
class ClickableApp
|
|
180
|
+
include Bubbletea::Model
|
|
181
|
+
|
|
182
|
+
ITEMS = ["Option A", "Option B", "Option C"]
|
|
183
|
+
|
|
184
|
+
def initialize
|
|
185
|
+
@selected = nil
|
|
186
|
+
@prefix = Bubblezone.new_prefix
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def init
|
|
190
|
+
[self, nil]
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def update(message)
|
|
194
|
+
case message
|
|
195
|
+
when Bubbletea::MouseMessage
|
|
196
|
+
if message.release? && (message.left? || message.button == 0)
|
|
197
|
+
result = Bubblezone.find_in_bounds(message.x, message.y)
|
|
198
|
+
if result
|
|
199
|
+
id, _zone = result
|
|
200
|
+
@selected = id.sub(@prefix, "").to_i
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
[self, nil]
|
|
204
|
+
when Bubbletea::KeyMessage
|
|
205
|
+
return [self, Bubbletea.quit] if message.to_s == "q"
|
|
206
|
+
[self, nil]
|
|
207
|
+
else
|
|
208
|
+
[self, nil]
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def view
|
|
213
|
+
lines = ITEMS.map.with_index do |item, i|
|
|
214
|
+
marker = i == @selected ? "[x]" : "[ ]"
|
|
215
|
+
content = "#{marker} #{item}"
|
|
216
|
+
Bubblezone.mark("#{@prefix}#{i}", content)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
Bubblezone.scan(lines.join("\n"))
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
Bubbletea.run(ClickableApp.new, alt_screen: true, mouse_cell_motion: true)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Integration with Lipgloss
|
|
227
|
+
|
|
228
|
+
**Style your content first:**
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
require "lipgloss"
|
|
232
|
+
require "bubblezone"
|
|
233
|
+
|
|
234
|
+
Bubblezone.new_global
|
|
235
|
+
|
|
236
|
+
button_style = Lipgloss::Style.new
|
|
237
|
+
.background("#7D56F4")
|
|
238
|
+
.foreground("#FFFFFF")
|
|
239
|
+
.padding(0, 3)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Mark the fully styled content:**
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
styled_button = button_style.render("Click Me")
|
|
246
|
+
clickable_button = Bubblezone.mark("btn", styled_button)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Scan to register zones:**
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
output = Bubblezone.scan(clickable_button)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## API Reference
|
|
256
|
+
|
|
257
|
+
### Module Methods (Global Manager)
|
|
258
|
+
|
|
259
|
+
| Method | Description |
|
|
260
|
+
|--------|-------------|
|
|
261
|
+
| `Bubblezone.new_global` | Initialize the global zone manager |
|
|
262
|
+
| `Bubblezone.close` | Close the global manager |
|
|
263
|
+
| `Bubblezone.enabled?` | Check if zone tracking is enabled |
|
|
264
|
+
| `Bubblezone.enabled = bool` | Enable/disable zone tracking |
|
|
265
|
+
| `Bubblezone.new_prefix` | Generate a unique zone ID prefix |
|
|
266
|
+
| `Bubblezone.mark(id, text)` | Wrap text with zone markers |
|
|
267
|
+
| `Bubblezone.scan(text)` | Parse zones and strip markers |
|
|
268
|
+
| `Bubblezone.get(id)` | Get ZoneInfo for an ID (or nil) |
|
|
269
|
+
| `Bubblezone.clear(id)` | Remove a stored zone |
|
|
270
|
+
| `Bubblezone.clear_all` | Remove all stored zones |
|
|
271
|
+
| `Bubblezone.zone_ids` | Get array of all tracked zone IDs |
|
|
272
|
+
|
|
273
|
+
### Iteration Methods
|
|
274
|
+
|
|
275
|
+
| Method | Description |
|
|
276
|
+
|--------|-------------|
|
|
277
|
+
| `Bubblezone.each_in_bounds(x, y) { \|id, zone\| }` | Yield each zone containing coordinates |
|
|
278
|
+
| `Bubblezone.any_in_bounds?(x, y)` | Check if any zone contains coordinates |
|
|
279
|
+
| `Bubblezone.find_in_bounds(x, y)` | Get first `[id, zone]` pair, or nil |
|
|
280
|
+
|
|
281
|
+
### Manager Class
|
|
282
|
+
|
|
283
|
+
| Method | Description |
|
|
284
|
+
|--------|-------------|
|
|
285
|
+
| `Manager.new` | Create a new zone manager |
|
|
286
|
+
| `#close` | Close the manager |
|
|
287
|
+
| `#enabled?` / `#enabled=` | Get/set enabled state |
|
|
288
|
+
| `#new_prefix` | Generate unique prefix |
|
|
289
|
+
| `#mark(id, text)` | Mark text with zone |
|
|
290
|
+
| `#scan(text)` | Parse and strip markers |
|
|
291
|
+
| `#get(id)` | Get zone info |
|
|
292
|
+
| `#clear(id)` | Clear specific zone |
|
|
293
|
+
| `#clear_all` | Clear all zones |
|
|
294
|
+
| `#zone_ids` | Get all zone IDs |
|
|
295
|
+
| `#each_in_bounds(x, y)` | Iterate matching zones |
|
|
296
|
+
| `#any_in_bounds?(x, y)` | Check for any match |
|
|
297
|
+
| `#find_in_bounds(x, y)` | Get first match |
|
|
298
|
+
|
|
299
|
+
### ZoneInfo Class
|
|
300
|
+
|
|
301
|
+
| Method | Description |
|
|
302
|
+
|--------|-------------|
|
|
303
|
+
| `#start_x`, `#start_y` | Zone start coordinates |
|
|
304
|
+
| `#end_x`, `#end_y` | Zone end coordinates |
|
|
305
|
+
| `#in_bounds?(x, y)` | Check if coordinates are within zone |
|
|
306
|
+
| `#zero?` | Check if zone has no position data |
|
|
307
|
+
| `#pos(x, y)` | Get relative position within zone |
|
|
308
|
+
|
|
309
|
+
## Important Notes
|
|
310
|
+
|
|
311
|
+
### Zone Processing is Asynchronous
|
|
312
|
+
|
|
313
|
+
The Go bubblezone library processes zones asynchronously. After calling `scan`, there may be a brief delay before zones are available via `get`. In interactive applications with Bubbletea, this is typically not an issue as mouse events occur after rendering.
|
|
314
|
+
|
|
315
|
+
### Coordinate Systems
|
|
316
|
+
|
|
317
|
+
When using `alt_screen: true` with Bubbletea, mouse coordinates are relative to (0, 0) at the top-left of the screen, matching zone coordinates exactly. Without alt screen, you may need to account for terminal scroll position.
|
|
318
|
+
|
|
319
|
+
### Order of Operations
|
|
320
|
+
|
|
321
|
+
1. Style your content with Lipgloss
|
|
322
|
+
2. Mark the styled content with `Bubblezone.mark`
|
|
323
|
+
3. Build your complete layout
|
|
324
|
+
4. Call `Bubblezone.scan` on the final output
|
|
325
|
+
5. Handle mouse events using `get` or `find_in_bounds`
|
|
326
|
+
|
|
327
|
+
## Development
|
|
328
|
+
|
|
329
|
+
**Requirements:**
|
|
330
|
+
- Go 1.23+
|
|
331
|
+
- Ruby 3.2+
|
|
332
|
+
|
|
333
|
+
**Install dependencies:**
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
bundle install
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**Build the Go library and compile the extension:**
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
bundle exec rake compile
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
**Run tests:**
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
bundle exec rake test
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Run demos:**
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
./demo/clickable_alt
|
|
355
|
+
./demo/clickable_list
|
|
356
|
+
./demo/clickable_simple
|
|
357
|
+
./demo/full_layout
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Contributing
|
|
361
|
+
|
|
362
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/marcoroth/bubblezone-ruby.
|
|
363
|
+
|
|
364
|
+
## License
|
|
365
|
+
|
|
366
|
+
The gem is available as open source under the terms of the MIT License.
|
|
367
|
+
|
|
368
|
+
## Acknowledgments
|
|
369
|
+
|
|
370
|
+
This gem wraps [lrstanley/bubblezone](https://github.com/lrstanley/bubblezone), which provides zone tracking for terminal UIs and builds on the excellent [Charm](https://charm.sh) ecosystem, including [lipgloss](https://github.com/marcoroth/lipgloss-ruby) and [bubbletea](https://github.com/marcoroth/bubbletea-ruby).
|
data/bubblezone.gemspec
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/bubblezone/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "bubblezone"
|
|
7
|
+
spec.version = Bubblezone::VERSION
|
|
8
|
+
spec.authors = ["Marco Roth"]
|
|
9
|
+
spec.email = ["marco.roth@intergga.ch"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Ruby bindings for bubblezone, helper utility for BubbleTea, allowing easy mouse event tracking."
|
|
12
|
+
spec.description = "Ruby bindings for the bubblezone, providing zone management for terminal applications with mouse support."
|
|
13
|
+
spec.homepage = "https://github.com/marcoroth/bubblezone-ruby"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/marcoroth/bubblezone-ruby"
|
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/marcoroth/bubblezone-ruby/releases"
|
|
20
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
21
|
+
|
|
22
|
+
spec.files = Dir[
|
|
23
|
+
"bubblezone.gemspec",
|
|
24
|
+
"LICENSE.txt",
|
|
25
|
+
"README.md",
|
|
26
|
+
"lib/**/*.rb",
|
|
27
|
+
"ext/**/*.{c,h,rb}",
|
|
28
|
+
"go/**/*.{go,mod,sum}",
|
|
29
|
+
"go/build/**/*"
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
spec.bindir = "exe"
|
|
33
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
34
|
+
spec.require_paths = ["lib"]
|
|
35
|
+
|
|
36
|
+
spec.extensions = ["ext/bubblezone/extconf.rb"]
|
|
37
|
+
|
|
38
|
+
spec.add_dependency "rake-compiler", "~> 1.2"
|
|
39
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "mkmf"
|
|
4
|
+
|
|
5
|
+
extension_name = "bubblezone"
|
|
6
|
+
|
|
7
|
+
def detect_platform
|
|
8
|
+
cpu = RbConfig::CONFIG["host_cpu"]
|
|
9
|
+
os = RbConfig::CONFIG["host_os"]
|
|
10
|
+
|
|
11
|
+
arch = case cpu
|
|
12
|
+
when /aarch64|arm64/ then "arm64"
|
|
13
|
+
when /x86_64|amd64/ then "amd64"
|
|
14
|
+
when /arm/ then "arm"
|
|
15
|
+
when /i[3-6]86/ then "386"
|
|
16
|
+
else cpu
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
goos = case os
|
|
20
|
+
when /darwin/ then "darwin"
|
|
21
|
+
when /mswin|mingw/ then "windows"
|
|
22
|
+
else "linux"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
"#{goos}_#{arch}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
platform = detect_platform
|
|
29
|
+
go_lib_dir = File.expand_path("../../go/build/#{platform}", __dir__)
|
|
30
|
+
|
|
31
|
+
puts "Looking for Go library in: #{go_lib_dir}"
|
|
32
|
+
|
|
33
|
+
unless File.exist?(File.join(go_lib_dir, "libbubblezone.a"))
|
|
34
|
+
abort <<~ERROR
|
|
35
|
+
Could not find libbubblezone.a for platform #{platform}
|
|
36
|
+
|
|
37
|
+
Please build the Go archive first:
|
|
38
|
+
cd go && go build -buildmode=c-archive -o build/#{platform}/libbubblezone.a .
|
|
39
|
+
|
|
40
|
+
Or run:
|
|
41
|
+
bundle exec rake go:build
|
|
42
|
+
ERROR
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
$LDFLAGS << " -L#{go_lib_dir}"
|
|
46
|
+
$INCFLAGS << " -I#{go_lib_dir}"
|
|
47
|
+
|
|
48
|
+
$LOCAL_LIBS << " #{go_lib_dir}/libbubblezone.a"
|
|
49
|
+
|
|
50
|
+
case RbConfig::CONFIG["host_os"]
|
|
51
|
+
when /darwin/
|
|
52
|
+
$LDFLAGS << " -framework CoreFoundation -framework Security -framework SystemConfiguration"
|
|
53
|
+
$LDFLAGS << " -lresolv"
|
|
54
|
+
when /linux/
|
|
55
|
+
$LDFLAGS << " -lpthread -lm -ldl"
|
|
56
|
+
$LDFLAGS << " -lresolv" if find_library("resolv", "res_query")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
$srcs = [
|
|
60
|
+
"extension.c",
|
|
61
|
+
"manager.c",
|
|
62
|
+
"zone_info.c",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
create_makefile("#{extension_name}/#{extension_name}")
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#include "extension.h"
|
|
2
|
+
|
|
3
|
+
VALUE mBubblezone;
|
|
4
|
+
VALUE cManager;
|
|
5
|
+
VALUE cZoneInfo;
|
|
6
|
+
|
|
7
|
+
static VALUE bubblezone_new_global_rb(VALUE self) {
|
|
8
|
+
bubblezone_new_global();
|
|
9
|
+
return Qnil;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static VALUE bubblezone_global_close_rb(VALUE self) {
|
|
13
|
+
bubblezone_global_close();
|
|
14
|
+
return Qnil;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static VALUE bubblezone_global_set_enabled_rb(VALUE self, VALUE enabled) {
|
|
18
|
+
bubblezone_global_set_enabled(RTEST(enabled) ? 1 : 0);
|
|
19
|
+
return enabled;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static VALUE bubblezone_global_enabled_rb(VALUE self) {
|
|
23
|
+
return bubblezone_global_enabled() ? Qtrue : Qfalse;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static VALUE bubblezone_global_new_prefix_rb(VALUE self) {
|
|
27
|
+
char *prefix = bubblezone_global_new_prefix();
|
|
28
|
+
VALUE rb_prefix = rb_utf8_str_new_cstr(prefix);
|
|
29
|
+
bubblezone_free(prefix);
|
|
30
|
+
|
|
31
|
+
return rb_prefix;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static VALUE bubblezone_global_mark_rb(VALUE self, VALUE zone_id, VALUE text) {
|
|
35
|
+
Check_Type(zone_id, T_STRING);
|
|
36
|
+
Check_Type(text, T_STRING);
|
|
37
|
+
|
|
38
|
+
char *result = bubblezone_global_mark(StringValueCStr(zone_id), StringValueCStr(text));
|
|
39
|
+
VALUE rb_result = rb_utf8_str_new_cstr(result);
|
|
40
|
+
bubblezone_free(result);
|
|
41
|
+
|
|
42
|
+
return rb_result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static VALUE bubblezone_global_clear_rb(VALUE self, VALUE zone_id) {
|
|
46
|
+
Check_Type(zone_id, T_STRING);
|
|
47
|
+
bubblezone_global_clear(StringValueCStr(zone_id));
|
|
48
|
+
return Qnil;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static VALUE bubblezone_global_get_rb(VALUE self, VALUE zone_id) {
|
|
52
|
+
Check_Type(zone_id, T_STRING);
|
|
53
|
+
|
|
54
|
+
unsigned long long handle = bubblezone_global_get(StringValueCStr(zone_id));
|
|
55
|
+
|
|
56
|
+
if (handle == 0) {
|
|
57
|
+
return Qnil;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return zone_info_wrap(cZoneInfo, handle);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
static VALUE bubblezone_global_scan_rb(VALUE self, VALUE text) {
|
|
64
|
+
Check_Type(text, T_STRING);
|
|
65
|
+
|
|
66
|
+
char *result = bubblezone_global_scan(StringValueCStr(text));
|
|
67
|
+
VALUE rb_result = rb_utf8_str_new_cstr(result);
|
|
68
|
+
bubblezone_free(result);
|
|
69
|
+
|
|
70
|
+
return rb_result;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static VALUE bubblezone_upstream_version_rb(VALUE self) {
|
|
74
|
+
char *version = bubblezone_upstream_version();
|
|
75
|
+
VALUE rb_version = rb_utf8_str_new_cstr(version);
|
|
76
|
+
bubblezone_free(version);
|
|
77
|
+
|
|
78
|
+
return rb_version;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static VALUE bubblezone_version_rb(VALUE self) {
|
|
82
|
+
VALUE gem_version = rb_const_get(self, rb_intern("VERSION"));
|
|
83
|
+
VALUE upstream_version = bubblezone_upstream_version_rb(self);
|
|
84
|
+
VALUE format_string = rb_utf8_str_new_cstr("bubblezone v%s (upstream %s) [Go native extension]");
|
|
85
|
+
|
|
86
|
+
return rb_funcall(rb_mKernel, rb_intern("sprintf"), 3, format_string, gem_version, upstream_version);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
__attribute__((__visibility__("default"))) void Init_bubblezone(void) {
|
|
90
|
+
mBubblezone = rb_define_module("Bubblezone");
|
|
91
|
+
|
|
92
|
+
Init_bubblezone_manager();
|
|
93
|
+
Init_bubblezone_zone_info();
|
|
94
|
+
|
|
95
|
+
rb_define_singleton_method(mBubblezone, "new_global", bubblezone_new_global_rb, 0);
|
|
96
|
+
rb_define_singleton_method(mBubblezone, "close", bubblezone_global_close_rb, 0);
|
|
97
|
+
rb_define_singleton_method(mBubblezone, "enabled=", bubblezone_global_set_enabled_rb, 1);
|
|
98
|
+
rb_define_singleton_method(mBubblezone, "enabled?", bubblezone_global_enabled_rb, 0);
|
|
99
|
+
rb_define_singleton_method(mBubblezone, "new_prefix", bubblezone_global_new_prefix_rb, 0);
|
|
100
|
+
rb_define_singleton_method(mBubblezone, "mark", bubblezone_global_mark_rb, 2);
|
|
101
|
+
rb_define_singleton_method(mBubblezone, "clear", bubblezone_global_clear_rb, 1);
|
|
102
|
+
rb_define_singleton_method(mBubblezone, "get", bubblezone_global_get_rb, 1);
|
|
103
|
+
rb_define_singleton_method(mBubblezone, "scan", bubblezone_global_scan_rb, 1);
|
|
104
|
+
|
|
105
|
+
rb_define_singleton_method(mBubblezone, "upstream_version", bubblezone_upstream_version_rb, 0);
|
|
106
|
+
rb_define_singleton_method(mBubblezone, "version", bubblezone_version_rb, 0);
|
|
107
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#ifndef BUBBLEZONE_EXTENSION_H
|
|
2
|
+
#define BUBBLEZONE_EXTENSION_H
|
|
3
|
+
|
|
4
|
+
#include <ruby.h>
|
|
5
|
+
#include "libbubblezone.h"
|
|
6
|
+
|
|
7
|
+
extern VALUE mBubblezone;
|
|
8
|
+
extern VALUE cManager;
|
|
9
|
+
extern VALUE cZoneInfo;
|
|
10
|
+
|
|
11
|
+
typedef struct {
|
|
12
|
+
unsigned long long handle;
|
|
13
|
+
} bubblezone_manager_t;
|
|
14
|
+
|
|
15
|
+
typedef struct {
|
|
16
|
+
unsigned long long handle;
|
|
17
|
+
} bubblezone_zone_info_t;
|
|
18
|
+
|
|
19
|
+
#define GET_MANAGER(self, manager) \
|
|
20
|
+
bubblezone_manager_t *manager; \
|
|
21
|
+
TypedData_Get_Struct(self, bubblezone_manager_t, &manager_type, manager)
|
|
22
|
+
|
|
23
|
+
#define GET_ZONE_INFO(self, zone_info) \
|
|
24
|
+
bubblezone_zone_info_t *zone_info; \
|
|
25
|
+
TypedData_Get_Struct(self, bubblezone_zone_info_t, &zone_info_type, zone_info)
|
|
26
|
+
|
|
27
|
+
extern const rb_data_type_t manager_type;
|
|
28
|
+
extern const rb_data_type_t zone_info_type;
|
|
29
|
+
|
|
30
|
+
VALUE manager_wrap(VALUE klass, unsigned long long handle);
|
|
31
|
+
VALUE zone_info_wrap(VALUE klass, unsigned long long handle);
|
|
32
|
+
|
|
33
|
+
void Init_bubblezone_manager(void);
|
|
34
|
+
void Init_bubblezone_zone_info(void);
|
|
35
|
+
|
|
36
|
+
#endif
|