crystalruby 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +19 -16
- data/exe/crystalruby +3 -0
- data/lib/crystalruby/compilation.rb +0 -1
- data/lib/crystalruby/function.rb +19 -13
- data/lib/crystalruby/library.rb +8 -15
- data/lib/crystalruby/reactor.rb +4 -7
- data/lib/crystalruby/types/array.rb +4 -3
- data/lib/crystalruby/types/hash.rb +5 -3
- data/lib/crystalruby/types/named_tuple.rb +6 -3
- data/lib/crystalruby/types/time.rb +3 -1
- data/lib/crystalruby/types/tuple.rb +3 -1
- data/lib/crystalruby/types/typedef.rb +3 -1
- data/lib/crystalruby/types/union_type.rb +8 -6
- data/lib/crystalruby/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eeae38802618342fade685584872134cd905229adfe493619130bf008c649390
|
4
|
+
data.tar.gz: ff35b13a1ff26f2aa42063a91ebfea888efd03ffd4fabb32911baf70557a3cdb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43b28e5a1739a80ebe77064612fbf11024d9b6f7a9e3fd4357a0d3c6d99f90322953b34b484d7b8c010e40336fa1ba2d64bb4c59ec59bd0cef2ebaaea1fefd65
|
7
|
+
data.tar.gz: 70a9068038a5bc107ab6beb398491dd04cec70c2bf402dda35c80e70282033c2a1e7da482a15a0f378676835daee3eb220af66d02ab98a4d2be7991fb18d818d
|
data/README.md
CHANGED
@@ -258,7 +258,7 @@ E.g.
|
|
258
258
|
|
259
259
|
IntArrOrBoolArr = crtype{ Array(Bool) | Array(Int32) }
|
260
260
|
|
261
|
-
crystalize [a: IntArrOrBoolArr] => json{ IntArrOrBoolArr }
|
261
|
+
crystalize [a: json{ IntArrOrBoolArr }] => json{ IntArrOrBoolArr }
|
262
262
|
def method_with_named_types(a)
|
263
263
|
return a
|
264
264
|
end
|
@@ -273,16 +273,19 @@ Exceptions thrown in Crystal code can be caught in Ruby.
|
|
273
273
|
You can use any Crystal shards and write ordinary, stand-alone Crystal code.
|
274
274
|
|
275
275
|
The default entry point for the crystal shared library generated by the gem is
|
276
|
-
inside `./crystalruby/src/main.cr`.
|
276
|
+
inside `./crystalruby/{library_name}/src/main.cr`.
|
277
|
+
`{library_name}` defaults to `crystalruby` if you haven't explicitly specific a different library target.
|
277
278
|
|
278
|
-
|
279
|
+
This file is not automatically overridden by the gem, and is safe for you to define and require new files relative to this location to write additional stand-alone Crystal code.
|
280
|
+
|
281
|
+
You can define shard dependencies inside `./crystalruby/{library_name}/src/shard.yml`
|
279
282
|
Run the below to install new shards
|
280
283
|
|
281
284
|
```bash
|
282
285
|
bundle exec crystalruby install
|
283
286
|
```
|
284
287
|
|
285
|
-
Remember to require these
|
288
|
+
Remember to also require these dependencies after installing them to make them available to `crystalruby` code. E.g. inside `./crystalruby/{libraryname}/src/main.cr`
|
286
289
|
|
287
290
|
You can edit the default paths for crystal source and library files from within the `./crystalruby.yaml` config file.
|
288
291
|
|
@@ -309,7 +312,7 @@ MyModule.add("1", "2")
|
|
309
312
|
|
310
313
|
## Inline Chunks
|
311
314
|
|
312
|
-
`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
|
315
|
+
`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 operations or initializations.
|
313
316
|
|
314
317
|
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.
|
315
318
|
|
@@ -467,13 +470,13 @@ Then you can run this file as part of your build step, to ensure all Crystal cod
|
|
467
470
|
|
468
471
|
## Concurrency
|
469
472
|
|
470
|
-
While Ruby programs allow multi-threading, Crystal
|
473
|
+
While Ruby programs allow multi-threading, Crystal (if not using experimental multi-thread support) uses only a single thread and utilises Fiber based cooperative-multitasking to allow for concurrent execution. This means that by default, Crystal libraries can not safely be invoked in parallel across multiple Ruby threads.
|
471
474
|
|
472
|
-
To safely
|
475
|
+
To safely utilise `crystalruby` in a multithreaded environment, `crystalruby` implements a Reactor, which multiplexes all Ruby calls to Crystal across a single thread.
|
473
476
|
|
474
|
-
By default `crystalruby` methods are blocking/synchronous, this means that for blocking operations, a single crystalruby call can block the entire reactor.
|
477
|
+
By default `crystalruby` methods are blocking/synchronous, this means that for blocking operations, a single crystalruby call can block the entire reactor across _all_ threads.
|
475
478
|
|
476
|
-
To allow you to benefit from Crystal's fiber based concurrency, you can use the `async` option on crystalized ruby methods. This allows several Ruby threads to invoke Crystal code simultaneously.
|
479
|
+
To allow you to benefit from Crystal's fiber based concurrency, you can use the `async: true` option on crystalized ruby methods. This allows several Ruby threads to invoke Crystal code simultaneously.
|
477
480
|
|
478
481
|
E.g.
|
479
482
|
|
@@ -498,7 +501,7 @@ end
|
|
498
501
|
|
499
502
|
### Reactor performance
|
500
503
|
|
501
|
-
There is a small amount of synchronization overhead to multiplexing calls across a single thread. Ad-hoc testing amounts this to be
|
504
|
+
There is a small amount of synchronization overhead to multiplexing calls across a single thread. Ad-hoc testing on a fast machine amounts this to be within the order of 10 nanoseconds per call.
|
502
505
|
For most use-cases this overhead is negligible, especially if the bulk of your CPU heavy task occurs exclusively in Crystal code. However, if you are invoking very fast Crystal code from Ruby in a tight loop (e.g. a simple 1 + 2)
|
503
506
|
then the overhead of the reactor can become significant.
|
504
507
|
|
@@ -517,9 +520,9 @@ recompile Crystal code only when it detects changes to the embedded function or
|
|
517
520
|
|
518
521
|
## Multi-library support
|
519
522
|
|
520
|
-
Large Crystal projects are known to have long compile times. To mitigate this, `crystalruby` supports splitting your Crystal code into multiple libraries. This allows you to only recompile
|
521
|
-
To indicate which library a piece of embedded Crystal code belongs to, you can use the `
|
522
|
-
If the
|
523
|
+
Large Crystal projects are known to have long compile times. To mitigate this, `crystalruby` supports splitting your Crystal code into multiple libraries. This allows you to only recompile any libraries that have changed, rather than all crystalcode within the entire project.
|
524
|
+
To indicate which library a piece of embedded Crystal code belongs to, you can use the `lib` option in the `crystalize` and `crystal` methods.
|
525
|
+
If the `lib` option is not provided, the code will be compiled into the default library (simply named `crystalruby`).
|
523
526
|
|
524
527
|
```ruby
|
525
528
|
module Foo
|
@@ -534,12 +537,12 @@ module Foo
|
|
534
537
|
end
|
535
538
|
```
|
536
539
|
|
537
|
-
Naturally Crystal
|
538
|
-
|
540
|
+
Naturally, Crystal methods must reside in the same library to natively interact.
|
541
|
+
Cross library interaction can be facilitated via Ruby code.
|
539
542
|
|
540
543
|
## Troubleshooting
|
541
544
|
|
542
|
-
|
545
|
+
In cases where compiled assets are in left an invalid state, it can be useful to clear out generated assets and rebuild from scratch.
|
543
546
|
|
544
547
|
To do this execute:
|
545
548
|
|
data/exe/crystalruby
CHANGED
@@ -36,6 +36,9 @@ def clean
|
|
36
36
|
Dir["#{CrystalRuby.config.crystal_src_dir}/**/src/generated"].each do |codegen_dir|
|
37
37
|
FileUtils.rm_rf(codegen_dir)
|
38
38
|
end
|
39
|
+
Dir["#{CrystalRuby.config.crystal_src_dir}/**/lib"].each do |lib_dir|
|
40
|
+
FileUtils.rm_rf(lib_dir)
|
41
|
+
end
|
39
42
|
end
|
40
43
|
|
41
44
|
def build
|
data/lib/crystalruby/function.rb
CHANGED
@@ -6,7 +6,7 @@ module CrystalRuby
|
|
6
6
|
include Typemaps
|
7
7
|
include Config
|
8
8
|
|
9
|
-
attr_accessor :owner, :method_name, :args, :returns, :function_body, :lib, :async, :block
|
9
|
+
attr_accessor :owner, :method_name, :args, :returns, :function_body, :lib, :async, :block, :attached
|
10
10
|
|
11
11
|
def initialize(method:, args:, returns:, function_body:, lib:, async: false, &block)
|
12
12
|
self.owner = method.owner
|
@@ -17,12 +17,13 @@ module CrystalRuby
|
|
17
17
|
self.lib = lib
|
18
18
|
self.async = async
|
19
19
|
self.block = block
|
20
|
+
self.attached = false
|
20
21
|
end
|
21
22
|
|
22
23
|
# This is where we write/overwrite the class and instance methods
|
23
24
|
# with their crystalized equivalents.
|
24
25
|
# We also perform JIT compilation and JIT attachment of the FFI functions.
|
25
|
-
# Crystalized methods
|
26
|
+
# Crystalized methods can be redefined without restarting, if running in a live-reloading environment.
|
26
27
|
# If they are redefined with a different function body, the new function body
|
27
28
|
# will result in a new digest and the FFI function will be recompiled and reattached.
|
28
29
|
def define_crystalized_methods!(lib)
|
@@ -34,12 +35,12 @@ module CrystalRuby
|
|
34
35
|
lib.build!
|
35
36
|
return send(func.method_name, *args)
|
36
37
|
end
|
37
|
-
unless
|
38
|
+
unless func.attached?
|
38
39
|
should_reenter = func.attach_ffi_lib_functions!
|
39
40
|
return send(func.method_name, *args) if should_reenter
|
40
41
|
end
|
41
|
-
# All crystalruby functions are executed on the reactor to ensure
|
42
|
-
#
|
42
|
+
# All crystalruby functions are executed on the reactor to ensure Crystal/Ruby interop code is executed
|
43
|
+
# from a single same thread. (Needed to make GC and Fiber scheduler happy)
|
43
44
|
# Type mapping (if required) is applied on arguments and on return values.
|
44
45
|
func.map_retval(
|
45
46
|
Reactor.schedule_work!(
|
@@ -58,14 +59,11 @@ module CrystalRuby
|
|
58
59
|
# This is where we attach the top-level FFI functions of the shared object
|
59
60
|
# to our library (yield and init) needed for successful operation of the reactor.
|
60
61
|
# We also initialize the shared object (needed to start the GC) and
|
61
|
-
# start the reactor unless we are in single-thread mode.
|
62
|
+
# start the reactor, unless we are in single-thread mode.
|
62
63
|
def attach_ffi_lib_functions!
|
63
64
|
should_reenter = unwrapped?
|
64
65
|
lib_file = lib.lib_file
|
65
|
-
lib.
|
66
|
-
lib.methods.each_value do |method|
|
67
|
-
method.attach_ffi_func!
|
68
|
-
end
|
66
|
+
lib.methods.each_value(&:attach_ffi_func!)
|
69
67
|
lib.singleton_class.class_eval do
|
70
68
|
extend FFI::Library
|
71
69
|
ffi_lib lib_file
|
@@ -86,8 +84,8 @@ module CrystalRuby
|
|
86
84
|
should_reenter
|
87
85
|
end
|
88
86
|
|
89
|
-
#
|
90
|
-
#
|
87
|
+
# Attaches the crystalized FFI functions to their related Ruby modules and classes.
|
88
|
+
# If a wrapper block has been passed to the crystalize function,
|
91
89
|
# then the we also wrap the crystalized function using a prepended Module.
|
92
90
|
def attach_ffi_func!
|
93
91
|
argtypes = ffi_types
|
@@ -114,7 +112,7 @@ module CrystalRuby
|
|
114
112
|
owner.attach_function ffi_name, argtypes, rettype, blocking: true
|
115
113
|
around_wrapper_block = block
|
116
114
|
method_name = self.method_name
|
117
|
-
|
115
|
+
@attached = true
|
118
116
|
return unless around_wrapper_block
|
119
117
|
|
120
118
|
@around_wrapper ||= begin
|
@@ -136,6 +134,14 @@ module CrystalRuby
|
|
136
134
|
block && !@around_wrapper
|
137
135
|
end
|
138
136
|
|
137
|
+
def attached?
|
138
|
+
@attached
|
139
|
+
end
|
140
|
+
|
141
|
+
def unattach!
|
142
|
+
@attached = false
|
143
|
+
end
|
144
|
+
|
139
145
|
def ffi_name
|
140
146
|
lib_fn_name + (async && !config.single_thread_mode ? "_async" : "")
|
141
147
|
end
|
data/lib/crystalruby/library.rb
CHANGED
@@ -8,7 +8,7 @@ module CrystalRuby
|
|
8
8
|
CR_COMPILE_MUX = Mutex.new
|
9
9
|
CR_ATTACH_MUX = Mutex.new
|
10
10
|
|
11
|
-
attr_accessor :name, :methods, :chunks, :root_dir, :lib_dir, :src_dir, :codegen_dir, :
|
11
|
+
attr_accessor :name, :methods, :chunks, :root_dir, :lib_dir, :src_dir, :codegen_dir, :reactor
|
12
12
|
|
13
13
|
@libs_by_name = {}
|
14
14
|
|
@@ -30,13 +30,10 @@ module CrystalRuby
|
|
30
30
|
self.name = name
|
31
31
|
self.methods = {}
|
32
32
|
self.chunks = []
|
33
|
-
self.attachments = Hash.new(false)
|
34
33
|
initialize_library!
|
35
34
|
end
|
36
35
|
|
37
|
-
#
|
38
|
-
# bootstrap a library filesystem,
|
39
|
-
# and generate a top level index.cr and shard file if
|
36
|
+
# Bootstraps the library filesystem and generates top level index.cr and shard files if
|
40
37
|
# these do not already exist.
|
41
38
|
def initialize_library!
|
42
39
|
@root_dir, @lib_dir, @src_dir, @codegen_dir = [
|
@@ -57,11 +54,11 @@ module CrystalRuby
|
|
57
54
|
YAML
|
58
55
|
end
|
59
56
|
|
60
|
-
#
|
61
|
-
# and
|
57
|
+
# Generates and stores a reference to a new CrystalRuby::Function
|
58
|
+
# and triggers the generation of the crystal code. (See write_chunk)
|
62
59
|
def crystalize_method(method, args, returns, function_body, async, &block)
|
63
60
|
CR_ATTACH_MUX.synchronize do
|
64
|
-
|
61
|
+
methods.each_value(&:unattach!)
|
65
62
|
method_key = "#{method.owner.name}/#{method.name}"
|
66
63
|
methods[method_key] = Function.new(
|
67
64
|
method: method,
|
@@ -100,8 +97,7 @@ module CrystalRuby
|
|
100
97
|
end
|
101
98
|
|
102
99
|
def compiled?
|
103
|
-
|
104
|
-
File.exist?(lib_file) && chunks.all? do |chunk|
|
100
|
+
@compiled ||= File.exist?(lib_file) && chunks.all? do |chunk|
|
105
101
|
chunk_data = chunk[:body]
|
106
102
|
file_digest = Digest::MD5.hexdigest chunk_data
|
107
103
|
fname = chunk[:chunk_name]
|
@@ -115,10 +111,6 @@ module CrystalRuby
|
|
115
111
|
""
|
116
112
|
end
|
117
113
|
|
118
|
-
def attached?(owner)
|
119
|
-
attachments[owner]
|
120
|
-
end
|
121
|
-
|
122
114
|
def register_type!(type)
|
123
115
|
@types_cache ||= {}
|
124
116
|
@types_cache[type.name] = type.type_defn
|
@@ -183,7 +175,8 @@ module CrystalRuby
|
|
183
175
|
filename = (codegen_dir / module_name / "#{chunk_name}_#{file_digest}.cr").to_s
|
184
176
|
|
185
177
|
unless existing.delete(filename)
|
186
|
-
|
178
|
+
methods.each_value(&:unattach!)
|
179
|
+
@compiled = false
|
187
180
|
FileUtils.mkdir_p(codegen_dir / module_name)
|
188
181
|
File.write(filename, body)
|
189
182
|
end
|
data/lib/crystalruby/reactor.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
module CrystalRuby
|
2
|
-
# The Reactor represents a singleton Thread
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
# to allow safe invocation of Crystal code from across any number of Ruby threads.
|
7
|
-
# Functions annotated with async: true, are executed using callbacks to allow these to be multi-plexed in a non-blocking manner.
|
2
|
+
# The Reactor represents a singleton Thread responsible for running all Ruby/crystal interop code.
|
3
|
+
# Crystal's Fiber scheduler and GC assume all code is run on a single thread.
|
4
|
+
# This class is responsible for multiplexing Ruby and Crystal code on a single thread.
|
5
|
+
# Functions annotated with async: true, are executed using callbacks to allow these to be interleaved without blocking.
|
8
6
|
|
9
7
|
module Reactor
|
10
8
|
module_function
|
@@ -45,7 +43,6 @@ module CrystalRuby
|
|
45
43
|
error_type = error_type.to_sym
|
46
44
|
is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
|
47
45
|
error_type = is_exception_type ? Object.const_get(error_type) : RuntimeError
|
48
|
-
tid = tid.zero? ? Reactor.current_thread_id : tid
|
49
46
|
raise error_type.new(message) unless THREAD_MAP.key?(tid)
|
50
47
|
|
51
48
|
THREAD_MAP[tid][:error] = error_type.new(message)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CrystalRuby::Types
|
2
4
|
Array = Type.new(
|
3
5
|
:Array,
|
@@ -6,9 +8,8 @@ module CrystalRuby::Types
|
|
6
8
|
|
7
9
|
def self.Array(type)
|
8
10
|
Type.validate!(type)
|
9
|
-
Type.new("Array", inner_types: [type], accept_if: [::Array]
|
10
|
-
|
11
|
-
a.map!{|v| type.interpret!(v) }
|
11
|
+
Type.new("Array", inner_types: [type], accept_if: [::Array]) do |a|
|
12
|
+
a.map! { |v| type.interpret!(v) }
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CrystalRuby::Types
|
2
4
|
Hash = Type.new(
|
3
5
|
:Hash,
|
4
|
-
error: "Hash type must have 2 type parameters. E.g. Hash(Float64, String)"
|
6
|
+
error: "Hash type must have 2 type parameters. E.g. Hash(Float64, String)"
|
5
7
|
)
|
6
8
|
|
7
9
|
def self.Hash(key_type, value_type)
|
8
10
|
Type.validate!(key_type)
|
9
11
|
Type.validate!(value_type)
|
10
12
|
Type.new("Hash", inner_types: [key_type, value_type], accept_if: [::Hash]) do |h|
|
11
|
-
h.transform_keys!{|k| key_type.interpret!(k) }
|
12
|
-
h.transform_values!{|v| value_type.interpret!(v) }
|
13
|
+
h.transform_keys! { |k| key_type.interpret!(k) }
|
14
|
+
h.transform_values! { |v| value_type.interpret!(v) }
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CrystalRuby::Types
|
2
4
|
NamedTuple = Type.new(
|
3
5
|
:NamedTuple,
|
@@ -6,7 +8,7 @@ module CrystalRuby::Types
|
|
6
8
|
|
7
9
|
def self.NamedTuple(types_hash)
|
8
10
|
types_hash.keys.each do |key|
|
9
|
-
raise "NamedTuple keys must be symbols" unless key.
|
11
|
+
raise "NamedTuple keys must be symbols" unless key.is_a?(::Symbol) || key.respond_to?(:to_sym)
|
10
12
|
end
|
11
13
|
types_hash.values.each do |value_type|
|
12
14
|
Type.validate!(value_type)
|
@@ -14,9 +16,10 @@ module CrystalRuby::Types
|
|
14
16
|
keys = types_hash.keys.map(&:to_sym)
|
15
17
|
values = types_hash.values
|
16
18
|
Type.new("NamedTuple", inner_types: values, inner_keys: keys, accept_if: [::Hash]) do |h|
|
17
|
-
h.transform_keys!{|k| k.to_sym }
|
19
|
+
h.transform_keys! { |k| k.to_sym }
|
18
20
|
raise "Invalid keys for named tuple" unless h.keys.length == keys.length
|
19
|
-
raise "Invalid keys for named tuple" unless h.keys.all?{|k| keys.include?(k)}
|
21
|
+
raise "Invalid keys for named tuple" unless h.keys.all? { |k| keys.include?(k) }
|
22
|
+
|
20
23
|
h.each do |key, value|
|
21
24
|
h[key] = values[keys.index(key)].interpret!(value)
|
22
25
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CrystalRuby::Types
|
2
4
|
Tuple = Type.new(
|
3
5
|
:Tuple,
|
@@ -9,7 +11,7 @@ module CrystalRuby::Types
|
|
9
11
|
Type.validate!(value_type)
|
10
12
|
end
|
11
13
|
Type.new("Tuple", inner_types: types, accept_if: [::Array]) do |a|
|
12
|
-
a.map!.with_index{|v, i|
|
14
|
+
a.map!.with_index { |v, i| inner_types[i].interpret!(v) }
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "type_serializer"
|
2
4
|
|
3
5
|
module CrystalRuby
|
@@ -5,7 +7,7 @@ module CrystalRuby
|
|
5
7
|
class Typedef; end
|
6
8
|
|
7
9
|
def self.Typedef(type)
|
8
|
-
return type if type.
|
10
|
+
return type if type.is_a?(Class) && type < Typedef
|
9
11
|
|
10
12
|
Class.new(Typedef) do
|
11
13
|
define_singleton_method(:union_types) do
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CrystalRuby
|
2
4
|
module Types
|
3
5
|
class UnionType < Type
|
@@ -26,12 +28,12 @@ module CrystalRuby
|
|
26
28
|
|
27
29
|
def interpret!(raw)
|
28
30
|
union_types.each do |type|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
next unless type.interprets?(raw)
|
32
|
+
|
33
|
+
begin
|
34
|
+
return type.interpret!(raw)
|
35
|
+
rescue StandardError
|
36
|
+
# Pass
|
35
37
|
end
|
36
38
|
end
|
37
39
|
raise "Invalid deserialized value #{raw} for type #{inspect}"
|
data/lib/crystalruby/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: crystalruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-04-
|
11
|
+
date: 2024-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: digest
|