crystalruby 0.1.8 → 0.1.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|