chordsketch 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a85b62f230b39c41fb4729e4730390f817c6b74a478fb1e81eade536a1f52c2a
4
+ data.tar.gz: 88ebdf8f9af317a632bda4ccee5d3f1858d296870590926f90299e23ac548335
5
+ SHA512:
6
+ metadata.gz: 73edde31c61c7bb8a106b4fbd50c65c320785187c8911c2edc0762738c39ad6973e6b8a7111e5317691ff99641068756c6ff194b530f27dfb906b8c067903f07
7
+ data.tar.gz: 766614dcf8e2ce64906abe0471bc4299d0441529c72f4da77254a7a1f2ca329d1c0c237c51d3a662a4dafda5d9c5f4036822e1246fc398508a8083f0bf06c9ce
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ChordPro file format parser and renderer.
4
+ #
5
+ # This gem provides native bindings to the ChordSketch Rust library
6
+ # via UniFFI-generated Ruby code.
7
+ #
8
+ # @example Render ChordPro as text
9
+ # text = Chordsketch.parse_and_render_text("{title: Hello}\n[C]Hello", nil, nil)
10
+ #
11
+ # @example Render with transposition
12
+ # text = Chordsketch.parse_and_render_text(input, "guitar", 2)
13
+
14
+ require "ffi"
15
+ require "rbconfig"
16
+
17
+ # Detect the platform and pre-load the correct native library before
18
+ # requiring the UniFFI-generated bindings.
19
+ module Chordsketch
20
+ # @api private
21
+ module NativeLoader
22
+ PLATFORM_MAP = {
23
+ /x86_64.*linux/ => "x86_64-linux",
24
+ /aarch64.*linux/ => "aarch64-linux",
25
+ /arm64.*darwin|aarch64.*darwin/ => "aarch64-darwin",
26
+ /x86_64.*darwin/ => "x86_64-darwin",
27
+ /x64.*mingw|x64.*mswin/ => "x86_64-windows",
28
+ }.freeze
29
+
30
+ # Detect the platform-specific subdirectory for native libraries.
31
+ def self.detect_platform_dir
32
+ arch = RbConfig::CONFIG["arch"]
33
+ PLATFORM_MAP.each do |pattern, dir|
34
+ return dir if arch.match?(pattern)
35
+ end
36
+ raise "Unsupported platform: #{arch}. " \
37
+ "ChordSketch supports x86_64/aarch64 Linux, macOS, and x86_64 Windows."
38
+ end
39
+
40
+ # Resolve and validate the absolute path to the platform-specific
41
+ # native library, then expose it as `Chordsketch::NATIVE_LIB_PATH`
42
+ # so the UniFFI-generated bindings can pass it to `ffi_lib`.
43
+ #
44
+ # The previous approach of pre-loading via `FFI::DynamicLibrary.open`
45
+ # with `RTLD_GLOBAL` was unreliable: ffi gem 1.17+ explicitly opens
46
+ # its own handle by name in `ffi_lib` and ignores already-loaded
47
+ # global handles, so the bindings would fail with "Could not open
48
+ # library 'chordsketch_ffi'" even after a successful pre-load. See
49
+ # #1082.
50
+ def self.load!
51
+ platform_dir = detect_platform_dir
52
+ lib_dir = File.join(File.dirname(__FILE__), platform_dir)
53
+ lib_name = FFI.map_library_name("chordsketch_ffi")
54
+ lib_path = File.join(lib_dir, lib_name)
55
+
56
+ unless File.exist?(lib_path)
57
+ raise "Native library not found at #{lib_path}. " \
58
+ "Ensure the gem was built for your platform (#{platform_dir}). " \
59
+ "For local development, generate bindings with:\n" \
60
+ " cargo run -p chordsketch-ffi --bin uniffi-bindgen generate \\\n" \
61
+ " --library target/debug/libchordsketch_ffi.so \\\n" \
62
+ " --language ruby --out-dir packages/ruby/lib/"
63
+ end
64
+
65
+ Chordsketch.const_set(:NATIVE_LIB_PATH, lib_path)
66
+ end
67
+ end
68
+ end
69
+
70
+ Chordsketch::NativeLoader.load!
71
+
72
+ # Load the UniFFI-generated bindings. Their `ffi_lib` line is rewritten
73
+ # to reference `Chordsketch::NATIVE_LIB_PATH` (set above) by a sed step
74
+ # in the gem build pipeline. See `.github/workflows/ruby.yml` and #1082.
75
+ require_relative "chordsketch_uniffi"
@@ -0,0 +1,703 @@
1
+ # This file was autogenerated by some hot garbage in the `uniffi` crate.
2
+ # Trust me, you don't want to mess with it!
3
+
4
+ # Common helper code.
5
+ #
6
+ # Ideally this would live in a separate .rb file where it can be unittested etc
7
+ # in isolation, and perhaps even published as a re-useable package.
8
+ #
9
+ # However, it's important that the details of how this helper code works (e.g. the
10
+ # way that different builtin types are passed across the FFI) exactly match what's
11
+ # expected by the rust code on the other side of the interface. In practice right
12
+ # now that means coming from the exact some version of `uniffi` that was used to
13
+ # compile the rust component. The easiest way to ensure this is to bundle the Ruby
14
+ # helpers directly inline like we're doing here.
15
+
16
+ require 'ffi'
17
+
18
+
19
+ module Chordsketch
20
+ def self.uniffi_in_range(i, type_name, min, max)
21
+ raise TypeError, "no implicit conversion of #{i} into Integer" unless i.respond_to?(:to_int)
22
+ i = i.to_int
23
+ raise RangeError, "#{type_name} requires #{min} <= value < #{max}" unless (min <= i && i < max)
24
+ i
25
+ end
26
+
27
+ def self.uniffi_utf8(v)
28
+ raise TypeError, "no implicit conversion of #{v} into String" unless v.respond_to?(:to_str)
29
+ v = v.to_str.encode(Encoding::UTF_8)
30
+ raise Encoding::InvalidByteSequenceError, "not a valid UTF-8 encoded string" unless v.valid_encoding?
31
+ v
32
+ end
33
+
34
+ def self.uniffi_bytes(v)
35
+ raise TypeError, "no implicit conversion of #{v} into String" unless v.respond_to?(:to_str)
36
+ v.to_str
37
+ end
38
+
39
+ class RustBuffer < FFI::Struct
40
+ layout :capacity, :uint64,
41
+ :len, :uint64,
42
+ :data, :pointer
43
+
44
+ def self.alloc(size)
45
+ return Chordsketch.rust_call(:ffi_chordsketch_ffi_rustbuffer_alloc, size)
46
+ end
47
+
48
+ def self.reserve(rbuf, additional)
49
+ return Chordsketch.rust_call(:ffi_chordsketch_ffi_rustbuffer_reserve, rbuf, additional)
50
+ end
51
+
52
+ def free
53
+ Chordsketch.rust_call(:ffi_chordsketch_ffi_rustbuffer_free, self)
54
+ end
55
+
56
+ def capacity
57
+ self[:capacity]
58
+ end
59
+
60
+ def len
61
+ self[:len]
62
+ end
63
+
64
+ def len=(value)
65
+ self[:len] = value
66
+ end
67
+
68
+ def data
69
+ self[:data]
70
+ end
71
+
72
+ def to_s
73
+ "RustBuffer(capacity=#{capacity}, len=#{len}, data=#{data.read_bytes len})"
74
+ end
75
+
76
+ # The allocated buffer will be automatically freed if an error occurs, ensuring that
77
+ # we don't accidentally leak it.
78
+ def self.allocWithBuilder
79
+ builder = RustBufferBuilder.new
80
+
81
+ begin
82
+ yield builder
83
+ rescue => e
84
+ builder.discard
85
+ raise e
86
+ end
87
+ end
88
+
89
+ # The RustBuffer will be freed once the context-manager exits, ensuring that we don't
90
+ # leak it even if an error occurs.
91
+ def consumeWithStream
92
+ stream = RustBufferStream.new self
93
+
94
+ yield stream
95
+
96
+ raise RuntimeError, 'junk data left in buffer after consuming' if stream.remaining != 0
97
+ ensure
98
+ free
99
+ end# The primitive String type.
100
+
101
+ def self.allocFromString(value)
102
+ RustBuffer.allocWithBuilder do |builder|
103
+ builder.write value.encode('utf-8')
104
+ return builder.finalize
105
+ end
106
+ end
107
+
108
+ def consumeIntoString
109
+ consumeWithStream do |stream|
110
+ return stream.read(stream.remaining).force_encoding(Encoding::UTF_8)
111
+ end
112
+ end
113
+
114
+ # The primitive Bytes type.
115
+
116
+ def self.allocFromBytes(value)
117
+ RustBuffer.allocWithBuilder do |builder|
118
+ builder.write_Bytes(value)
119
+ return builder.finalize
120
+ end
121
+ end
122
+
123
+ def consumeIntoBytes
124
+ consumeWithStream do |stream|
125
+ return stream.readBytes
126
+ end
127
+ end
128
+
129
+
130
+
131
+ # The Optional<T> type for i8.
132
+
133
+ def self.check_lower_Optionali8(v)
134
+ if not v.nil?
135
+
136
+ end
137
+ end
138
+
139
+ def self.alloc_from_Optionali8(v)
140
+ RustBuffer.allocWithBuilder do |builder|
141
+ builder.write_Optionali8(v)
142
+ return builder.finalize()
143
+ end
144
+ end
145
+
146
+ def consumeIntoOptionali8
147
+ consumeWithStream do |stream|
148
+ return stream.readOptionali8
149
+ end
150
+ end
151
+
152
+ # The Optional<T> type for string.
153
+
154
+ def self.check_lower_Optionalstring(v)
155
+ if not v.nil?
156
+
157
+ end
158
+ end
159
+
160
+ def self.alloc_from_Optionalstring(v)
161
+ RustBuffer.allocWithBuilder do |builder|
162
+ builder.write_Optionalstring(v)
163
+ return builder.finalize()
164
+ end
165
+ end
166
+
167
+ def consumeIntoOptionalstring
168
+ consumeWithStream do |stream|
169
+ return stream.readOptionalstring
170
+ end
171
+ end
172
+
173
+ # The Sequence<T> type for string.
174
+
175
+ def self.check_lower_Sequencestring(v)
176
+ v.each do |item|
177
+
178
+ end
179
+ end
180
+
181
+ def self.alloc_from_Sequencestring(v)
182
+ RustBuffer.allocWithBuilder do |builder|
183
+ builder.write_Sequencestring(v)
184
+ return builder.finalize()
185
+ end
186
+ end
187
+
188
+ def consumeIntoSequencestring
189
+ consumeWithStream do |stream|
190
+ return stream.readSequencestring
191
+ end
192
+ end
193
+
194
+
195
+ end
196
+
197
+ module UniFFILib
198
+ class ForeignBytes < FFI::Struct
199
+ layout :len, :int32,
200
+ :data, :pointer
201
+
202
+ def len
203
+ self[:len]
204
+ end
205
+
206
+ def data
207
+ self[:data]
208
+ end
209
+
210
+ def to_s
211
+ "ForeignBytes(len=#{len}, data=#{data.read_bytes(len)})"
212
+ end
213
+ end
214
+ end
215
+
216
+ private_constant :UniFFILib
217
+
218
+ # Helper for structured reading of values from a RustBuffer.
219
+ class RustBufferStream
220
+
221
+ def initialize(rbuf)
222
+ @rbuf = rbuf
223
+ @offset = 0
224
+ end
225
+
226
+ def remaining
227
+ @rbuf.len - @offset
228
+ end
229
+
230
+ def read(size)
231
+ raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len
232
+
233
+ data = @rbuf.data.get_bytes @offset, size
234
+
235
+ @offset += size
236
+
237
+ data
238
+ end
239
+
240
+ def readI8
241
+ unpack_from 1, 'c'
242
+ end
243
+
244
+ def readString
245
+ size = unpack_from 4, 'l>'
246
+
247
+ raise InternalError, 'Unexpected negative string length' if size.negative?
248
+
249
+ read(size).force_encoding(Encoding::UTF_8)
250
+ end
251
+
252
+ def readBytes
253
+ size = unpack_from 4, 'l>'
254
+
255
+ raise InternalError, 'Unexpected negative byte string length' if size.negative?
256
+
257
+ read(size).force_encoding(Encoding::BINARY)
258
+ end
259
+
260
+
261
+
262
+
263
+
264
+ # The Error type ChordSketchError
265
+
266
+ def readTypeChordSketchError
267
+ variant = unpack_from 4, 'l>'
268
+
269
+ if variant == 1
270
+ return ChordSketchError::NoSongsFound.new
271
+ end
272
+ if variant == 2
273
+ return ChordSketchError::InvalidConfig.new(
274
+ readString()
275
+ )
276
+ end
277
+
278
+ raise InternalError, 'Unexpected variant tag for TypeChordSketchError'
279
+ end
280
+
281
+
282
+ # The Optional<T> type for i8.
283
+
284
+ def readOptionali8
285
+ flag = unpack_from 1, 'c'
286
+
287
+ if flag == 0
288
+ return nil
289
+ elsif flag == 1
290
+ return readI8
291
+ else
292
+ raise InternalError, 'Unexpected flag byte for Optionali8'
293
+ end
294
+ end
295
+
296
+ # The Optional<T> type for string.
297
+
298
+ def readOptionalstring
299
+ flag = unpack_from 1, 'c'
300
+
301
+ if flag == 0
302
+ return nil
303
+ elsif flag == 1
304
+ return readString
305
+ else
306
+ raise InternalError, 'Unexpected flag byte for Optionalstring'
307
+ end
308
+ end
309
+
310
+ # The Sequence<T> type for string.
311
+
312
+ def readSequencestring
313
+ count = unpack_from 4, 'l>'
314
+
315
+ raise InternalError, 'Unexpected negative sequence length' if count.negative?
316
+
317
+ items = []
318
+
319
+ count.times do
320
+ items.append readString
321
+ end
322
+
323
+ items
324
+ end
325
+
326
+
327
+
328
+ def unpack_from(size, format)
329
+ raise InternalError, 'read past end of rust buffer' if @offset + size > @rbuf.len
330
+
331
+ value = @rbuf.data.get_bytes(@offset, size).unpack format
332
+
333
+ @offset += size
334
+
335
+ # TODO: verify this
336
+ raise 'more than one element!!!' if value.size > 1
337
+
338
+ value[0]
339
+ end
340
+ end
341
+
342
+ private_constant :RustBufferStream
343
+
344
+ # Helper for structured writing of values into a RustBuffer.
345
+ class RustBufferBuilder
346
+ def initialize
347
+ @rust_buf = RustBuffer.alloc 16
348
+ @rust_buf.len = 0
349
+ end
350
+
351
+ def finalize
352
+ rbuf = @rust_buf
353
+
354
+ @rust_buf = nil
355
+
356
+ rbuf
357
+ end
358
+
359
+ def discard
360
+ return if @rust_buf.nil?
361
+
362
+ rbuf = finalize
363
+ rbuf.free
364
+ end
365
+
366
+ def write(value)
367
+ reserve(value.bytes.size) do
368
+ @rust_buf.data.put_array_of_char @rust_buf.len, value.bytes
369
+ end
370
+ end
371
+
372
+ def write_I8(v)
373
+ v = Chordsketch::uniffi_in_range(v, "i8", -2**7, 2**7)
374
+ pack_into(1, 'c', v)
375
+ end
376
+
377
+ def write_String(v)
378
+ v = Chordsketch::uniffi_utf8(v)
379
+ pack_into 4, 'l>', v.bytes.size
380
+ write v
381
+ end
382
+
383
+ def write_Bytes(v)
384
+ v = Chordsketch::uniffi_bytes(v)
385
+ pack_into 4, 'l>', v.bytes.size
386
+ write v
387
+ end
388
+
389
+
390
+
391
+ # The Optional<T> type for i8.
392
+
393
+ def write_Optionali8(v)
394
+ if v.nil?
395
+ pack_into(1, 'c', 0)
396
+ else
397
+ pack_into(1, 'c', 1)
398
+ self.write_I8(v)
399
+ end
400
+ end
401
+
402
+ # The Optional<T> type for string.
403
+
404
+ def write_Optionalstring(v)
405
+ if v.nil?
406
+ pack_into(1, 'c', 0)
407
+ else
408
+ pack_into(1, 'c', 1)
409
+ self.write_String(v)
410
+ end
411
+ end
412
+
413
+ # The Sequence<T> type for string.
414
+
415
+ def write_Sequencestring(items)
416
+ pack_into(4, 'l>', items.size)
417
+
418
+ items.each do |item|
419
+ self.write_String(item)
420
+ end
421
+ end
422
+
423
+
424
+
425
+ private
426
+
427
+ def reserve(num_bytes)
428
+ if @rust_buf.len + num_bytes > @rust_buf.capacity
429
+ @rust_buf = RustBuffer.reserve(@rust_buf, num_bytes)
430
+ end
431
+
432
+ yield
433
+
434
+ @rust_buf.len += num_bytes
435
+ end
436
+
437
+ def pack_into(size, format, value)
438
+ reserve(size) do
439
+ @rust_buf.data.put_array_of_char @rust_buf.len, [value].pack(format).bytes
440
+ end
441
+ end
442
+ end
443
+
444
+ private_constant :RustBufferBuilder
445
+
446
+ # Error definitions
447
+ class RustCallStatus < FFI::Struct
448
+ layout :code, :int8,
449
+ :error_buf, RustBuffer
450
+
451
+ def code
452
+ self[:code]
453
+ end
454
+
455
+ def error_buf
456
+ self[:error_buf]
457
+ end
458
+
459
+ def to_s
460
+ "RustCallStatus(code=#{self[:code]})"
461
+ end
462
+ end
463
+
464
+ # These match the values from the uniffi::rustcalls module
465
+ CALL_SUCCESS = 0
466
+ CALL_ERROR = 1
467
+ CALL_PANIC = 2
468
+
469
+
470
+ module ChordSketchError
471
+ class NoSongsFound < StandardError
472
+ def initialize()
473
+ super()
474
+ end
475
+
476
+ def to_s
477
+ "#{self.class.name}()"
478
+ end
479
+ end
480
+ class InvalidConfig < StandardError
481
+ def initialize(reason)
482
+ @reason = reason
483
+ super()
484
+ end
485
+
486
+ attr_reader :reason
487
+
488
+
489
+ def to_s
490
+ "#{self.class.name}(reason=#{@reason.inspect})"
491
+ end
492
+ end
493
+
494
+ end
495
+
496
+
497
+ # Map error modules to the RustBuffer method name that reads them
498
+ ERROR_MODULE_TO_READER_METHOD = {
499
+
500
+ ChordSketchError => :readTypeChordSketchError,
501
+
502
+ }
503
+
504
+ private_constant :ERROR_MODULE_TO_READER_METHOD, :CALL_SUCCESS, :CALL_ERROR, :CALL_PANIC,
505
+ :RustCallStatus
506
+
507
+ def self.consume_buffer_into_error(error_module, rust_buffer)
508
+ rust_buffer.consumeWithStream do |stream|
509
+ reader_method = ERROR_MODULE_TO_READER_METHOD[error_module]
510
+ return stream.send(reader_method)
511
+ end
512
+ end
513
+
514
+ class InternalError < StandardError
515
+ end
516
+
517
+ def self.rust_call(fn_name, *args)
518
+ # Call a rust function
519
+ rust_call_with_error(nil, fn_name, *args)
520
+ end
521
+
522
+ def self.rust_call_with_error(error_module, fn_name, *args)
523
+ # Call a rust function and handle errors
524
+ #
525
+ # Use this when the rust function returns a Result<>. error_module must be the error_module that corresponds to that Result.
526
+
527
+
528
+ # Note: RustCallStatus.new zeroes out the struct, which is exactly what we
529
+ # want to pass to Rust (code=0, error_buf=RustBuffer(len=0, capacity=0,
530
+ # data=NULL))
531
+ status = RustCallStatus.new
532
+ args << status
533
+
534
+ result = UniFFILib.public_send(fn_name, *args)
535
+
536
+ case status.code
537
+ when CALL_SUCCESS
538
+ result
539
+ when CALL_ERROR
540
+ if error_module.nil?
541
+ status.error_buf.free
542
+ raise InternalError, "CALL_ERROR with no error_module set"
543
+ else
544
+ raise consume_buffer_into_error(error_module, status.error_buf)
545
+ end
546
+ when CALL_PANIC
547
+ # When the rust code sees a panic, it tries to construct a RustBuffer
548
+ # with the message. But if that code panics, then it just sends back
549
+ # an empty buffer.
550
+ if status.error_buf.len > 0
551
+ raise InternalError, status.error_buf.consumeIntoString()
552
+ else
553
+ raise InternalError, "Rust panic"
554
+ end
555
+ else
556
+ raise InternalError, "Unknown call status: #{status.code}"
557
+ end
558
+ end
559
+
560
+ private_class_method :consume_buffer_into_error
561
+
562
+ # This is how we find and load the dynamic library provided by the component.
563
+ # For now we just look it up by name.
564
+ module UniFFILib
565
+ extend FFI::Library
566
+
567
+
568
+ ffi_lib Chordsketch::NATIVE_LIB_PATH
569
+
570
+
571
+ attach_function :uniffi_chordsketch_ffi_fn_func_parse_and_render_html,
572
+ [RustBuffer.by_value, RustBuffer.by_value, RustBuffer.by_value, RustCallStatus.by_ref],
573
+ RustBuffer.by_value
574
+ attach_function :uniffi_chordsketch_ffi_fn_func_parse_and_render_pdf,
575
+ [RustBuffer.by_value, RustBuffer.by_value, RustBuffer.by_value, RustCallStatus.by_ref],
576
+ RustBuffer.by_value
577
+ attach_function :uniffi_chordsketch_ffi_fn_func_parse_and_render_text,
578
+ [RustBuffer.by_value, RustBuffer.by_value, RustBuffer.by_value, RustCallStatus.by_ref],
579
+ RustBuffer.by_value
580
+ attach_function :uniffi_chordsketch_ffi_fn_func_validate,
581
+ [RustBuffer.by_value, RustCallStatus.by_ref],
582
+ RustBuffer.by_value
583
+ attach_function :uniffi_chordsketch_ffi_fn_func_version,
584
+ [RustCallStatus.by_ref],
585
+ RustBuffer.by_value
586
+ attach_function :ffi_chordsketch_ffi_rustbuffer_alloc,
587
+ [:uint64, RustCallStatus.by_ref],
588
+ RustBuffer.by_value
589
+ attach_function :ffi_chordsketch_ffi_rustbuffer_from_bytes,
590
+ [ForeignBytes, RustCallStatus.by_ref],
591
+ RustBuffer.by_value
592
+ attach_function :ffi_chordsketch_ffi_rustbuffer_free,
593
+ [RustBuffer.by_value, RustCallStatus.by_ref],
594
+ :void
595
+ attach_function :ffi_chordsketch_ffi_rustbuffer_reserve,
596
+ [RustBuffer.by_value, :uint64, RustCallStatus.by_ref],
597
+ RustBuffer.by_value
598
+ attach_function :uniffi_chordsketch_ffi_checksum_func_parse_and_render_html,
599
+ [RustCallStatus.by_ref],
600
+ :uint16
601
+ attach_function :uniffi_chordsketch_ffi_checksum_func_parse_and_render_pdf,
602
+ [RustCallStatus.by_ref],
603
+ :uint16
604
+ attach_function :uniffi_chordsketch_ffi_checksum_func_parse_and_render_text,
605
+ [RustCallStatus.by_ref],
606
+ :uint16
607
+ attach_function :uniffi_chordsketch_ffi_checksum_func_validate,
608
+ [RustCallStatus.by_ref],
609
+ :uint16
610
+ attach_function :uniffi_chordsketch_ffi_checksum_func_version,
611
+ [RustCallStatus.by_ref],
612
+ :uint16
613
+ attach_function :ffi_chordsketch_ffi_uniffi_contract_version,
614
+ [RustCallStatus.by_ref],
615
+ :uint32
616
+
617
+ end
618
+
619
+ # Public interface members begin here.
620
+
621
+
622
+
623
+
624
+
625
+
626
+
627
+ def self.parse_and_render_html(input, config_json, transpose)
628
+ input = Chordsketch::uniffi_utf8(input)
629
+
630
+
631
+ config_json = (config_json ? Chordsketch::uniffi_utf8(config_json) : nil)
632
+ RustBuffer.check_lower_Optionalstring(config_json)
633
+
634
+ transpose = (transpose ? Chordsketch::uniffi_in_range(transpose, "i8", -2**7, 2**7) : nil)
635
+ RustBuffer.check_lower_Optionali8(transpose)
636
+
637
+ result = Chordsketch.rust_call_with_error(ChordSketchError,:uniffi_chordsketch_ffi_fn_func_parse_and_render_html,RustBuffer.allocFromString(input),RustBuffer.alloc_from_Optionalstring(config_json),RustBuffer.alloc_from_Optionali8(transpose))
638
+ return result.consumeIntoString
639
+ end
640
+
641
+
642
+
643
+
644
+
645
+ def self.parse_and_render_pdf(input, config_json, transpose)
646
+ input = Chordsketch::uniffi_utf8(input)
647
+
648
+
649
+ config_json = (config_json ? Chordsketch::uniffi_utf8(config_json) : nil)
650
+ RustBuffer.check_lower_Optionalstring(config_json)
651
+
652
+ transpose = (transpose ? Chordsketch::uniffi_in_range(transpose, "i8", -2**7, 2**7) : nil)
653
+ RustBuffer.check_lower_Optionali8(transpose)
654
+
655
+ result = Chordsketch.rust_call_with_error(ChordSketchError,:uniffi_chordsketch_ffi_fn_func_parse_and_render_pdf,RustBuffer.allocFromString(input),RustBuffer.alloc_from_Optionalstring(config_json),RustBuffer.alloc_from_Optionali8(transpose))
656
+ return result.consumeIntoBytes
657
+ end
658
+
659
+
660
+
661
+
662
+
663
+ def self.parse_and_render_text(input, config_json, transpose)
664
+ input = Chordsketch::uniffi_utf8(input)
665
+
666
+
667
+ config_json = (config_json ? Chordsketch::uniffi_utf8(config_json) : nil)
668
+ RustBuffer.check_lower_Optionalstring(config_json)
669
+
670
+ transpose = (transpose ? Chordsketch::uniffi_in_range(transpose, "i8", -2**7, 2**7) : nil)
671
+ RustBuffer.check_lower_Optionali8(transpose)
672
+
673
+ result = Chordsketch.rust_call_with_error(ChordSketchError,:uniffi_chordsketch_ffi_fn_func_parse_and_render_text,RustBuffer.allocFromString(input),RustBuffer.alloc_from_Optionalstring(config_json),RustBuffer.alloc_from_Optionali8(transpose))
674
+ return result.consumeIntoString
675
+ end
676
+
677
+
678
+
679
+
680
+
681
+ def self.validate(input)
682
+ input = Chordsketch::uniffi_utf8(input)
683
+
684
+
685
+ result = Chordsketch.rust_call(:uniffi_chordsketch_ffi_fn_func_validate,RustBuffer.allocFromString(input))
686
+ return result.consumeIntoSequencestring
687
+ end
688
+
689
+
690
+
691
+
692
+
693
+ def self.version()
694
+ result = Chordsketch.rust_call(:uniffi_chordsketch_ffi_fn_func_version,)
695
+ return result.consumeIntoString
696
+ end
697
+
698
+
699
+
700
+
701
+
702
+ end
703
+
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chordsketch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - koedame
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-04-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ description: Parse and render ChordPro files to text, HTML, and PDF. Native bindings
28
+ via UniFFI for high performance.
29
+ email:
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - lib/aarch64-darwin/libchordsketch_ffi.dylib
35
+ - lib/aarch64-linux/libchordsketch_ffi.so
36
+ - lib/chordsketch.rb
37
+ - lib/chordsketch_uniffi.rb
38
+ - lib/x86_64-darwin/libchordsketch_ffi.dylib
39
+ - lib/x86_64-linux/libchordsketch_ffi.so
40
+ - lib/x86_64-windows/chordsketch_ffi.dll
41
+ homepage: https://github.com/koedame/chordsketch
42
+ licenses:
43
+ - MIT
44
+ metadata:
45
+ source_code_uri: https://github.com/koedame/chordsketch
46
+ bug_tracker_uri: https://github.com/koedame/chordsketch/issues
47
+ changelog_uri: https://github.com/koedame/chordsketch/releases
48
+ rubygems_mfa_required: 'true'
49
+ post_install_message:
50
+ rdoc_options: []
51
+ require_paths:
52
+ - lib
53
+ required_ruby_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '3.0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubygems_version: 3.5.22
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: ChordPro file format parser and renderer
68
+ test_files: []