crystalruby 0.1.8 → 0.1.10
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/README.md +127 -2
- data/lib/crystalruby/template.rb +1 -1
- data/lib/crystalruby/templates/inline_chunk.cr +6 -0
- data/lib/crystalruby/version.rb +1 -1
- data/lib/crystalruby.rb +21 -4
- 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: '0499023eb26e35e6db77566876a96357202c0e6cdfafbb49d2aaa67e070c38cd'
|
4
|
+
data.tar.gz: 4e5f185097b4ad5091b079fc87b43d8d950119c7fbe659a7d76fe473d8cc7cd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d27665a59a9ecc7345bcca13c5e057d71166684350d54bc158bcefe82655025fc6a8c7fd08069f92c17abe849ad465ee995f223b32391e3f49f2ff7f30c8b92
|
7
|
+
data.tar.gz: ee56f2aead72e533e989b3e80c9550a65fcf78b2677dc070f697d656b1293eefecf0782fa43ac45255fced794a6a54040c32e21571723a1208b3d27babe71851
|
data/README.md
CHANGED
@@ -87,7 +87,7 @@ module Cache
|
|
87
87
|
end
|
88
88
|
|
89
89
|
crystalize [key: :string, value: :string] => :string
|
90
|
-
def redis_set_and_return(key)
|
90
|
+
def redis_set_and_return(key, value)
|
91
91
|
redis = Redis::Client.new
|
92
92
|
redis.set(key, value)
|
93
93
|
Cache.redis_get(key)
|
@@ -295,6 +295,131 @@ end
|
|
295
295
|
MyModule.add("1", "2")
|
296
296
|
```
|
297
297
|
|
298
|
+
## Inline Chunks
|
299
|
+
|
300
|
+
`crystalruby` also allows you to write inline Crystal code that does not require binding to Ruby. This can be useful for e.g. performing setup or teardown operations.
|
301
|
+
|
302
|
+
Follow these steps for a toy example of how we can use crystalized ruby and inline chunks to expose the [crystal-redis](https://github.com/stefanwille/crystal-redis) library to Ruby.
|
303
|
+
|
304
|
+
1. Start our toy project
|
305
|
+
|
306
|
+
```bash
|
307
|
+
mkdir crystalredis
|
308
|
+
cd crystalredis
|
309
|
+
bundle init
|
310
|
+
```
|
311
|
+
|
312
|
+
2. Add dependencies to our Gemfile and run `bundle install`
|
313
|
+
|
314
|
+
```ruby
|
315
|
+
# frozen_string_literal: true
|
316
|
+
|
317
|
+
source "https://rubygems.org"
|
318
|
+
|
319
|
+
gem 'crystalruby'
|
320
|
+
|
321
|
+
# Let's see if performance is comparable to that of the redis gem.
|
322
|
+
gem 'benchmark-ips'
|
323
|
+
gem 'redis'
|
324
|
+
```
|
325
|
+
|
326
|
+
3. Write our Redis client
|
327
|
+
|
328
|
+
```ruby
|
329
|
+
# Filename: crystalredis.rb
|
330
|
+
require 'crystalruby'
|
331
|
+
|
332
|
+
module CrystalRedis
|
333
|
+
|
334
|
+
crystal do
|
335
|
+
CLIENT = Redis.new
|
336
|
+
def self.client
|
337
|
+
CLIENT
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
crystalize [key: :string, value: :string] => :void
|
342
|
+
def set(key, value)
|
343
|
+
client.set(key, value)
|
344
|
+
end
|
345
|
+
|
346
|
+
crystalize [key: :string] => :string
|
347
|
+
def get(key)
|
348
|
+
client.get(key).to_s
|
349
|
+
end
|
350
|
+
end
|
351
|
+
```
|
352
|
+
|
353
|
+
4. Load the modules (without running them) to generate our Crystal project skeleton.
|
354
|
+
|
355
|
+
```bash
|
356
|
+
bundle exec ruby crystalredis.rb
|
357
|
+
```
|
358
|
+
|
359
|
+
5. Add the missing Redis dependency to our shard.yml
|
360
|
+
|
361
|
+
```yaml
|
362
|
+
# filename: crystalruby/src/shard.yml
|
363
|
+
dependencies:
|
364
|
+
redis:
|
365
|
+
github: stefanwille/crystal-redis
|
366
|
+
```
|
367
|
+
|
368
|
+
```ruby
|
369
|
+
# filename: main.cr
|
370
|
+
require "redis"
|
371
|
+
require "./generated/index"
|
372
|
+
```
|
373
|
+
|
374
|
+
```bash
|
375
|
+
bundle exec crystalruby install
|
376
|
+
```
|
377
|
+
|
378
|
+
6. Compile and benchmark our new module in Ruby
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
# Filename: benchmark.rb
|
382
|
+
# Let's compare the performance of our CrystalRedis module to the Ruby Redis gem
|
383
|
+
require_relative "crystalredis"
|
384
|
+
require 'redis'
|
385
|
+
require 'benchmark/ips'
|
386
|
+
|
387
|
+
Benchmark.ips do |x|
|
388
|
+
rbredis = Redis.new
|
389
|
+
|
390
|
+
x.report(:crredis) do
|
391
|
+
CrystalRedis.set("hello", "world")
|
392
|
+
CrystalRedis.get("hello")
|
393
|
+
end
|
394
|
+
|
395
|
+
x.report(:rbredis) do
|
396
|
+
rbredis.set("hello", "world")
|
397
|
+
rbredis.get("hello")
|
398
|
+
end
|
399
|
+
end
|
400
|
+
```
|
401
|
+
|
402
|
+
7. Run the benchmark
|
403
|
+
|
404
|
+
```bash
|
405
|
+
$ bundle exec ruby benchmark.rb
|
406
|
+
```
|
407
|
+
|
408
|
+
### Output
|
409
|
+
|
410
|
+
```bash
|
411
|
+
|
412
|
+
#crystalredis wins! (Warm up during first run will be slow for crredis, due to first compilation)
|
413
|
+
|
414
|
+
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin22]
|
415
|
+
Warming up --------------------------------------
|
416
|
+
crredis 1.946k i/100ms
|
417
|
+
rbredis 1.749k i/100ms
|
418
|
+
Calculating -------------------------------------
|
419
|
+
crredis 22.319k (± 1.7%) i/s - 112.868k in 5.058448s
|
420
|
+
rbredis 16.861k (± 9.1%) i/s - 83.952k in 5.024941s
|
421
|
+
```
|
422
|
+
|
298
423
|
## Release Builds
|
299
424
|
|
300
425
|
You can control whether CrystalRuby builds in debug or release mode by setting following config option
|
@@ -340,7 +465,7 @@ bundle exec crystalruby clean
|
|
340
465
|
|
341
466
|
## Design Goals
|
342
467
|
|
343
|
-
`crystalruby`'s primary purpose is provide ergonomic access to Crystal from Ruby, over FFI.
|
468
|
+
`crystalruby`'s primary purpose is to provide ergonomic access to Crystal from Ruby, over FFI.
|
344
469
|
For simple usage, advanced knowledge of Crystal should not be required.
|
345
470
|
|
346
471
|
However, the abstraction it provides should remain simple, transparent, and easy to hack on and it should not preclude users from supplementing its capabilities with a more direct integration using ffi primtives.
|
data/lib/crystalruby/template.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module CrystalRuby
|
2
2
|
module Template
|
3
3
|
Dir[File.join(File.dirname(__FILE__), "templates", "*.cr")].each do |file|
|
4
|
-
template_name = File.basename(file, File.extname(file)).capitalize
|
4
|
+
template_name = File.basename(file, File.extname(file)).split("_").map(&:capitalize).join
|
5
5
|
const_set(template_name, File.read(file))
|
6
6
|
end
|
7
7
|
|
data/lib/crystalruby/version.rb
CHANGED
data/lib/crystalruby.rb
CHANGED
@@ -22,6 +22,19 @@ module CrystalRuby
|
|
22
22
|
@crystalize_next = { raw: type.to_sym == :raw, args: args, returns: returns, block: block }
|
23
23
|
end
|
24
24
|
|
25
|
+
def crystal(type = :src, &block)
|
26
|
+
inline_crystal_body = Template.render(
|
27
|
+
Template::InlineChunk,
|
28
|
+
{
|
29
|
+
module_name: name,
|
30
|
+
body: block.source.lines[
|
31
|
+
type == :raw ? 2...-2 : 1...-1
|
32
|
+
].join("\n")
|
33
|
+
}
|
34
|
+
)
|
35
|
+
CrystalRuby.write_chunk(self, body: inline_crystal_body)
|
36
|
+
end
|
37
|
+
|
25
38
|
def crtype(&block)
|
26
39
|
TypeBuilder.with_injected_type_dsl(self) do
|
27
40
|
TypeBuilder.build(&block)
|
@@ -56,7 +69,7 @@ module CrystalRuby
|
|
56
69
|
args ||= {}
|
57
70
|
@crystalize_next = nil
|
58
71
|
function = build_function(self, method_name, args, returns, function_body)
|
59
|
-
CrystalRuby.
|
72
|
+
CrystalRuby.write_chunk(self, name: function[:name], body: function[:body]) do
|
60
73
|
extend FFI::Library
|
61
74
|
ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
|
62
75
|
attach_function method_name, fname, function[:ffi_types], function[:return_ffi_type]
|
@@ -73,7 +86,10 @@ module CrystalRuby
|
|
73
86
|
receiver.prepend(Module.new do
|
74
87
|
define_method(method_name) do |*args|
|
75
88
|
CrystalRuby.compile! unless CrystalRuby.compiled?
|
76
|
-
|
89
|
+
unless CrystalRuby.attached?
|
90
|
+
CrystalRuby.attach!
|
91
|
+
return send(method_name, *args) if block
|
92
|
+
end
|
77
93
|
args.each_with_index do |arg, i|
|
78
94
|
args[i] = function[:arg_maps][i][arg] if function[:arg_maps][i]
|
79
95
|
end
|
@@ -226,6 +242,7 @@ module CrystalRuby
|
|
226
242
|
extend FFI::Library
|
227
243
|
ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
|
228
244
|
attach_function "init!", :init, [:pointer], :void
|
245
|
+
send(:remove_const, :ErrorCallback) if defined?(ErrorCallback)
|
229
246
|
const_set(:ErrorCallback, FFI::Function.new(:void, %i[string string]) do |error_type, message|
|
230
247
|
error_type = error_type.to_sym
|
231
248
|
is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
|
@@ -314,7 +331,7 @@ module CrystalRuby
|
|
314
331
|
|
315
332
|
def self.attach!
|
316
333
|
@block_store.each do |function|
|
317
|
-
function[:compile_callback]
|
334
|
+
function[:compile_callback]&.call
|
318
335
|
end
|
319
336
|
@attached = true
|
320
337
|
end
|
@@ -335,7 +352,7 @@ module CrystalRuby
|
|
335
352
|
File.read(digest_file_name) if File.exist?(digest_file_name)
|
336
353
|
end
|
337
354
|
|
338
|
-
def self.
|
355
|
+
def self.write_chunk(owner, body:, name: Digest::MD5.hexdigest(body), &compile_callback)
|
339
356
|
@block_store ||= []
|
340
357
|
@block_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
|
341
358
|
FileUtils.mkdir_p("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}")
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: crystalruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
@@ -87,6 +87,7 @@ files:
|
|
87
87
|
- lib/crystalruby/template.rb
|
88
88
|
- lib/crystalruby/templates/function.cr
|
89
89
|
- lib/crystalruby/templates/index.cr
|
90
|
+
- lib/crystalruby/templates/inline_chunk.cr
|
90
91
|
- lib/crystalruby/typebuilder.rb
|
91
92
|
- lib/crystalruby/typemaps.rb
|
92
93
|
- lib/crystalruby/types.rb
|