rubyx-py 0.1.1 → 0.2.1

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.
@@ -73,6 +73,7 @@ mod tests {
73
73
  use crate::stream::SendableValue;
74
74
  use crate::test_helpers::{skip_if_no_python, with_ruby_python};
75
75
  use crossbeam_channel::{bounded, unbounded};
76
+ use magnus::encoding::EncodingCapable;
76
77
  use magnus::value::ReprValue;
77
78
  use magnus::TryConvert;
78
79
  use serial_test::serial;
@@ -947,4 +948,171 @@ mod tests {
947
948
  assert_eq!(v, 99);
948
949
  });
949
950
  }
951
+
952
+ // ========== SendableValue::Bytes → Ruby String (ASCII-8BIT) ==========
953
+
954
+ #[test]
955
+ #[serial]
956
+ fn test_sendable_bytes_converts_to_ruby_string() {
957
+ with_ruby_python(|ruby, _api| {
958
+ let val: magnus::Value = SendableValue::Bytes(b"hello".to_vec())
959
+ .try_into()
960
+ .expect("Bytes conversion should succeed");
961
+
962
+ // Must be a Ruby String, not an Array of integers
963
+ let rstr = magnus::RString::try_convert(val).expect("should be a String");
964
+ let content = unsafe { rstr.as_slice() };
965
+ assert_eq!(content, b"hello");
966
+
967
+ // Verify encoding is ASCII-8BIT
968
+ let enc = rstr.enc_get();
969
+ let ascii_8bit = ruby
970
+ .find_encindex("ASCII-8BIT")
971
+ .expect("ASCII-8BIT must exist");
972
+ assert!(
973
+ enc == ascii_8bit,
974
+ "Bytes should produce ASCII-8BIT encoded String"
975
+ );
976
+ });
977
+ }
978
+
979
+ #[test]
980
+ #[serial]
981
+ fn test_sendable_bytes_empty() {
982
+ with_ruby_python(|ruby, _api| {
983
+ let val: magnus::Value = SendableValue::Bytes(Vec::new())
984
+ .try_into()
985
+ .expect("empty Bytes conversion should succeed");
986
+
987
+ let rstr = magnus::RString::try_convert(val).expect("should be a String");
988
+ let content = unsafe { rstr.as_slice() };
989
+ assert!(content.is_empty());
990
+
991
+ let enc = rstr.enc_get();
992
+ let ascii_8bit = ruby
993
+ .find_encindex("ASCII-8BIT")
994
+ .expect("ASCII-8BIT must exist");
995
+ assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
996
+ });
997
+ }
998
+
999
+ #[test]
1000
+ #[serial]
1001
+ fn test_sendable_bytes_with_null_bytes() {
1002
+ with_ruby_python(|ruby, _api| {
1003
+ let data = vec![0x00, 0x01, 0xff, 0x00, 0xfe];
1004
+ let val: magnus::Value = SendableValue::Bytes(data.clone())
1005
+ .try_into()
1006
+ .expect("Bytes with NULs should convert");
1007
+
1008
+ let rstr = magnus::RString::try_convert(val).expect("should be a String");
1009
+ let content = unsafe { rstr.as_slice() };
1010
+ assert_eq!(content, data.as_slice());
1011
+
1012
+ let enc = rstr.enc_get();
1013
+ let ascii_8bit = ruby
1014
+ .find_encindex("ASCII-8BIT")
1015
+ .expect("ASCII-8BIT must exist");
1016
+ assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
1017
+ });
1018
+ }
1019
+
1020
+ #[test]
1021
+ #[serial]
1022
+ fn test_sendable_bytes_all_256_values() {
1023
+ with_ruby_python(|ruby, _api| {
1024
+ let data: Vec<u8> = (0..=255).collect();
1025
+ let val: magnus::Value = SendableValue::Bytes(data.clone())
1026
+ .try_into()
1027
+ .expect("all byte values should convert");
1028
+
1029
+ let rstr = magnus::RString::try_convert(val).expect("should be a String");
1030
+ let content = unsafe { rstr.as_slice() };
1031
+ assert_eq!(content, data.as_slice());
1032
+
1033
+ let enc = rstr.enc_get();
1034
+ let ascii_8bit = ruby
1035
+ .find_encindex("ASCII-8BIT")
1036
+ .expect("ASCII-8BIT must exist");
1037
+ assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
1038
+ });
1039
+ }
1040
+
1041
+ #[test]
1042
+ #[serial]
1043
+ fn test_sendable_bytes_is_not_array() {
1044
+ with_ruby_python(|_ruby, _api| {
1045
+ let val: magnus::Value = SendableValue::Bytes(b"test".to_vec())
1046
+ .try_into()
1047
+ .expect("Bytes conversion should succeed");
1048
+
1049
+ // Must NOT be an Array (Vec<u8>.into_value would produce Array<Integer>)
1050
+ assert!(
1051
+ magnus::RArray::try_convert(val).is_err(),
1052
+ "Bytes must not convert to Ruby Array"
1053
+ );
1054
+ });
1055
+ }
1056
+
1057
+ #[test]
1058
+ #[serial]
1059
+ fn test_sendable_bytes_in_stream_delivery() {
1060
+ with_ruby_python(|ruby, _api| {
1061
+ let (tx, rx) = unbounded();
1062
+ let (cancel_tx, _cancel_rx) = bounded(1);
1063
+
1064
+ thread::spawn(move || {
1065
+ tx.send(Some(SendableValue::Integer(1))).ok();
1066
+ tx.send(Some(SendableValue::Bytes(b"\xde\xad".to_vec())))
1067
+ .ok();
1068
+ tx.send(Some(SendableValue::Str("after".to_string()))).ok();
1069
+ tx.send(None).ok();
1070
+ });
1071
+
1072
+ let mut stream = crate::stream::AsyncStream::from_channel(rx, cancel_tx);
1073
+
1074
+ // First: integer
1075
+ let val = stream.next().unwrap().unwrap();
1076
+ assert_eq!(i64::try_convert(val).unwrap(), 1);
1077
+
1078
+ // Second: bytes → ASCII-8BIT String
1079
+ let val = stream.next().unwrap().unwrap();
1080
+ let rstr = magnus::RString::try_convert(val).expect("should be String");
1081
+ let content = unsafe { rstr.as_slice() };
1082
+ assert_eq!(content, b"\xde\xad");
1083
+ let enc = rstr.enc_get();
1084
+ let ascii_8bit = ruby
1085
+ .find_encindex("ASCII-8BIT")
1086
+ .expect("ASCII-8BIT must exist");
1087
+ assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
1088
+
1089
+ // Third: regular string
1090
+ let val = stream.next().unwrap().unwrap();
1091
+ assert_eq!(String::try_convert(val).unwrap(), "after");
1092
+
1093
+ assert!(stream.next().is_none());
1094
+ });
1095
+ }
1096
+
1097
+ #[test]
1098
+ #[serial]
1099
+ fn test_sendable_bytes_large_payload() {
1100
+ with_ruby_python(|ruby, _api| {
1101
+ let data: Vec<u8> = (0..10_000).map(|i| (i % 256) as u8).collect();
1102
+ let val: magnus::Value = SendableValue::Bytes(data.clone())
1103
+ .try_into()
1104
+ .expect("large Bytes should convert");
1105
+
1106
+ let rstr = magnus::RString::try_convert(val).expect("should be a String");
1107
+ let content = unsafe { rstr.as_slice() };
1108
+ assert_eq!(content.len(), 10_000);
1109
+ assert_eq!(content, data.as_slice());
1110
+
1111
+ let enc = rstr.enc_get();
1112
+ let ascii_8bit = ruby
1113
+ .find_encindex("ASCII-8BIT")
1114
+ .expect("ASCII-8BIT must exist");
1115
+ assert!(enc == ascii_8bit, "encoding should be ASCII-8BIT");
1116
+ });
1117
+ }
950
1118
  }
@@ -1,8 +1,9 @@
1
- use crate::api;
2
1
  use crate::python_ffi::PyObject;
3
2
  use crate::ruby_helpers::runtime_error;
4
- use crate::rubyx_object::python_to_sendable;
3
+ use crate::rubyx_object::{python_to_sendable, RubyxObject};
4
+ use crate::{api, ruby_helpers};
5
5
  use crossbeam_channel::{bounded, unbounded, Receiver, Sender};
6
+ use magnus::encoding::EncodingCapable;
6
7
  use magnus::value::ReprValue;
7
8
  use magnus::{IntoValue, Value};
8
9
  use std::thread;
@@ -21,8 +22,11 @@ pub(crate) enum SendableValue {
21
22
  Float(f64),
22
23
  Str(String),
23
24
  Bool(bool),
25
+ Bytes(Vec<u8>),
26
+ Set(Vec<SendableValue>),
24
27
  List(Vec<SendableValue>),
25
28
  Dict(Vec<(SendableValue, SendableValue)>),
29
+ PyObjectRef(usize),
26
30
  }
27
31
  impl TryInto<magnus::Value> for SendableValue {
28
32
  type Error = magnus::Error;
@@ -37,7 +41,15 @@ impl TryInto<magnus::Value> for SendableValue {
37
41
  SendableValue::Float(f) => f.into_value_with(&ruby),
38
42
  SendableValue::Str(s) => s.as_str().into_value_with(&ruby),
39
43
  SendableValue::Bool(b) => b.into_value_with(&ruby),
40
- SendableValue::List(l) => {
44
+ SendableValue::Bytes(b) => {
45
+ let s = ruby.str_from_slice(&b);
46
+ s.enc_associate(ruby.find_encoding("ASCII-8BIT").ok_or(magnus::Error::new(
47
+ ruby_helpers::no_method_error(),
48
+ "No ASCII-8BIT",
49
+ ))?)?;
50
+ s.as_value()
51
+ }
52
+ SendableValue::List(l) | SendableValue::Set(l) => {
41
53
  let ruby_array = ruby.ary_new_capa(l.len());
42
54
  for item in l {
43
55
  let val: Value = item.try_into()?;
@@ -54,6 +66,19 @@ impl TryInto<magnus::Value> for SendableValue {
54
66
  }
55
67
  hash.as_value()
56
68
  }
69
+ SendableValue::PyObjectRef(addr) => {
70
+ let py_obj = addr as *mut PyObject;
71
+ let api = crate::API.get().ok_or_else(|| {
72
+ magnus::Error::new(runtime_error(), "Python API not initialized")
73
+ })?;
74
+ // RubyxObject::new increfs internally and python_to_sendable also incref
75
+ let wrapper = RubyxObject::new(py_obj, api).ok_or_else(|| {
76
+ magnus::Error::new(runtime_error(), "Failed to wrap Python object")
77
+ })?;
78
+ // Balance the extra incref from python_to_sendable
79
+ api.decref(py_obj);
80
+ wrapper.into_value_with(&ruby)
81
+ }
57
82
  };
58
83
  Ok(result)
59
84
  }
@@ -710,4 +735,201 @@ mod tests {
710
735
  assert_eq!(sum, (0..1000i64).sum::<i64>());
711
736
  });
712
737
  }
738
+
739
+ // ========== PyObjectRef: SendableValue → RubyxObject ==========
740
+
741
+ #[test]
742
+ #[serial]
743
+ fn test_py_object_ref_converts_to_rubyx_object() {
744
+ with_ruby_python(|_ruby, api| {
745
+ let os = api.import_module("os").expect("os should import");
746
+ api.incref(os); // incref for PyObjectRef (simulates what python_to_sendable does)
747
+ let sendable = SendableValue::PyObjectRef(os as usize);
748
+
749
+ let val: Value = sendable.try_into().expect("PyObjectRef should convert");
750
+ // The result should be a RubyxObject, not a primitive
751
+ assert!(!val.is_nil());
752
+ // Verify it's not a primitive type
753
+ assert!(i64::try_convert(val).is_err(), "should not be an Integer");
754
+ assert!(String::try_convert(val).is_err(), "should not be a String");
755
+
756
+ api.decref(os); // balance the import_module refcount
757
+ });
758
+ }
759
+
760
+ #[test]
761
+ #[serial]
762
+ fn test_py_object_ref_in_stream() {
763
+ with_ruby_python(|_ruby, api| {
764
+ let os = api.import_module("os").expect("os should import");
765
+ api.incref(os);
766
+
767
+ let (tx, rx) = unbounded();
768
+ let (cancel_tx, _cancel_rx) = bounded(1);
769
+ let addr = os as usize;
770
+
771
+ thread::spawn(move || {
772
+ tx.send(Some(SendableValue::Integer(1))).ok();
773
+ tx.send(Some(SendableValue::PyObjectRef(addr))).ok();
774
+ tx.send(Some(SendableValue::Str("after".to_string()))).ok();
775
+ tx.send(None).ok();
776
+ });
777
+
778
+ let mut stream = AsyncStream::from_channel(rx, cancel_tx);
779
+
780
+ // First: integer
781
+ let val = stream.next().unwrap().unwrap();
782
+ assert_eq!(i64::try_convert(val).unwrap(), 1);
783
+
784
+ // Second: PyObjectRef → RubyxObject
785
+ let val = stream.next().unwrap().unwrap();
786
+ assert!(!val.is_nil());
787
+ assert!(
788
+ i64::try_convert(val).is_err(),
789
+ "should be RubyxObject, not Integer"
790
+ );
791
+
792
+ // Third: string
793
+ let val = stream.next().unwrap().unwrap();
794
+ assert_eq!(String::try_convert(val).unwrap(), "after");
795
+
796
+ assert!(stream.next().is_none());
797
+ api.decref(os);
798
+ });
799
+ }
800
+
801
+ // ========== Set: SendableValue::Set → Ruby Array ==========
802
+
803
+ #[test]
804
+ #[serial]
805
+ fn test_set_converts_to_ruby_array() {
806
+ with_ruby_python(|_ruby, _api| {
807
+ let (tx, rx) = unbounded();
808
+ let (cancel_tx, _cancel_rx) = bounded(1);
809
+
810
+ thread::spawn(move || {
811
+ tx.send(Some(SendableValue::Set(vec![
812
+ SendableValue::Integer(1),
813
+ SendableValue::Integer(2),
814
+ SendableValue::Integer(3),
815
+ ])))
816
+ .ok();
817
+ tx.send(None).ok();
818
+ });
819
+
820
+ let mut stream = AsyncStream::from_channel(rx, cancel_tx);
821
+ let val = stream.next().unwrap().unwrap();
822
+
823
+ let arr = magnus::RArray::try_convert(val).unwrap();
824
+ assert_eq!(arr.len(), 3);
825
+
826
+ let mut items: Vec<i64> = (0..arr.len())
827
+ .map(|i| i64::try_convert(arr.entry::<Value>(i as isize).unwrap()).unwrap())
828
+ .collect();
829
+ items.sort();
830
+ assert_eq!(items, vec![1, 2, 3]);
831
+
832
+ assert!(stream.next().is_none());
833
+ });
834
+ }
835
+
836
+ #[test]
837
+ #[serial]
838
+ fn test_empty_set_converts_to_empty_ruby_array() {
839
+ with_ruby_python(|_ruby, _api| {
840
+ let (tx, rx) = unbounded();
841
+ let (cancel_tx, _cancel_rx) = bounded(1);
842
+
843
+ thread::spawn(move || {
844
+ tx.send(Some(SendableValue::Set(vec![]))).ok();
845
+ tx.send(None).ok();
846
+ });
847
+
848
+ let mut stream = AsyncStream::from_channel(rx, cancel_tx);
849
+ let val = stream.next().unwrap().unwrap();
850
+
851
+ let arr = magnus::RArray::try_convert(val).unwrap();
852
+ assert_eq!(arr.len(), 0);
853
+
854
+ assert!(stream.next().is_none());
855
+ });
856
+ }
857
+
858
+ #[test]
859
+ #[serial]
860
+ fn test_set_with_mixed_types_converts_to_ruby_array() {
861
+ with_ruby_python(|_ruby, _api| {
862
+ let (tx, rx) = unbounded();
863
+ let (cancel_tx, _cancel_rx) = bounded(1);
864
+
865
+ thread::spawn(move || {
866
+ tx.send(Some(SendableValue::Set(vec![
867
+ SendableValue::Integer(42),
868
+ SendableValue::Str("hello".to_string()),
869
+ SendableValue::Float(3.14),
870
+ SendableValue::Bool(true),
871
+ ])))
872
+ .ok();
873
+ tx.send(None).ok();
874
+ });
875
+
876
+ let mut stream = AsyncStream::from_channel(rx, cancel_tx);
877
+ let val = stream.next().unwrap().unwrap();
878
+
879
+ let arr = magnus::RArray::try_convert(val).unwrap();
880
+ assert_eq!(arr.len(), 4);
881
+
882
+ assert_eq!(
883
+ i64::try_convert(arr.entry::<Value>(0).unwrap()).unwrap(),
884
+ 42
885
+ );
886
+ assert_eq!(
887
+ String::try_convert(arr.entry::<Value>(1).unwrap()).unwrap(),
888
+ "hello"
889
+ );
890
+ assert!(
891
+ (f64::try_convert(arr.entry::<Value>(2).unwrap()).unwrap() - 3.14).abs() < 1e-9
892
+ );
893
+ assert!(bool::try_convert(arr.entry::<Value>(3).unwrap()).unwrap());
894
+
895
+ assert!(stream.next().is_none());
896
+ });
897
+ }
898
+
899
+ #[test]
900
+ #[serial]
901
+ fn test_set_in_stream_with_other_types() {
902
+ with_ruby_python(|_ruby, _api| {
903
+ let (tx, rx) = unbounded();
904
+ let (cancel_tx, _cancel_rx) = bounded(1);
905
+
906
+ thread::spawn(move || {
907
+ tx.send(Some(SendableValue::Integer(1))).ok();
908
+ tx.send(Some(SendableValue::Set(vec![
909
+ SendableValue::Integer(10),
910
+ SendableValue::Integer(20),
911
+ ])))
912
+ .ok();
913
+ tx.send(Some(SendableValue::Str("after".to_string()))).ok();
914
+ tx.send(None).ok();
915
+ });
916
+
917
+ let mut stream = AsyncStream::from_channel(rx, cancel_tx);
918
+
919
+ // First: integer
920
+ let val = stream.next().unwrap().unwrap();
921
+ assert_eq!(i64::try_convert(val).unwrap(), 1);
922
+
923
+ // Second: set → array
924
+ let val = stream.next().unwrap().unwrap();
925
+ let arr = magnus::RArray::try_convert(val).unwrap();
926
+ assert_eq!(arr.len(), 2);
927
+
928
+ // Third: string
929
+ let val = stream.next().unwrap().unwrap();
930
+ assert_eq!(String::try_convert(val).unwrap(), "after");
931
+
932
+ assert!(stream.next().is_none());
933
+ });
934
+ }
713
935
  }
data/lib/rubyx/railtie.rb CHANGED
@@ -14,7 +14,8 @@ module Rubyx
14
14
 
15
15
  # Register rake tasks
16
16
  rake_tasks do
17
- load 'rubyx/tasks/rubyx.rake'
17
+ task_file = File.expand_path('tasks/rubyx.rake', __dir__)
18
+ load task_file if File.exist?(task_file)
18
19
  end
19
20
  end
20
21
  end
@@ -0,0 +1,127 @@
1
+ namespace :rubyx do
2
+ desc 'Initialize Python environment (downloads uv and Python if needed)'
3
+ task init: :environment do
4
+ Rubyx::Rails.init!
5
+ puts '[Rubyx] Python environment initialized successfully.'
6
+ end
7
+
8
+ desc 'Check Python environment health'
9
+ task check: :environment do
10
+ puts 'Checking Python environment...'
11
+ puts
12
+
13
+ # Check uv
14
+ system_uv = `which uv 2>/dev/null`.strip
15
+ uv_available = !system_uv.empty? && File.exist?(system_uv)
16
+ puts "uv available: #{uv_available ? "Yes (#{system_uv})" : 'No (will auto-download)'}"
17
+
18
+ begin
19
+ Rubyx::Rails.ensure_initialized!
20
+ puts 'Python initialized: Yes'
21
+ rescue => e
22
+ puts "Python initialized: No (#{e.message})"
23
+ puts
24
+ puts 'Run `rake rubyx:init` to initialize.'
25
+ exit 1
26
+ end
27
+
28
+ begin
29
+ Rubyx.eval('1 + 1')
30
+ puts 'Basic eval: OK'
31
+ rescue => e
32
+ puts "Basic eval: FAILED (#{e.message})"
33
+ exit 1
34
+ end
35
+
36
+ begin
37
+ gen = Rubyx.eval("import sys\niter([sys.version.split()[0]])")
38
+ version = Rubyx.stream(gen).first
39
+ puts "Import sys: OK (Python #{version})"
40
+ rescue => e
41
+ puts "Import sys: FAILED (#{e.message})"
42
+ exit 1
43
+ end
44
+
45
+ puts
46
+ puts 'All checks passed!'
47
+ end
48
+
49
+ desc 'Show Rubyx configuration and status'
50
+ task status: :environment do
51
+ config = Rubyx::Rails.configuration
52
+
53
+ puts 'Rubyx Status'
54
+ puts '=' * 40
55
+
56
+ puts "Initialized: #{Rubyx::Rails.initialized?}"
57
+ puts
58
+
59
+ puts 'Configuration:'
60
+ puts " pyproject_path: #{config.pyproject_path || '(not set)'}"
61
+ puts " pyproject_content: #{config.pyproject_content ? '(inline, %d bytes)' % config.pyproject_content.length : '(not set)'}"
62
+ puts " auto_init: #{config.auto_init}"
63
+ puts " force_reinit: #{config.force_reinit}"
64
+ puts " uv_version: #{config.uv_version}"
65
+ puts " debug: #{config.debug}"
66
+ puts " python_paths: #{config.python_paths.inspect}"
67
+ puts " uv_path: #{config.uv_path || '(auto-download)'}"
68
+ puts " uv_args: #{config.uv_args.inspect}"
69
+ puts
70
+
71
+ if config.pyproject_path
72
+ exists = File.exist?(config.pyproject_path.to_s)
73
+ puts "pyproject.toml exists: #{exists}"
74
+ end
75
+
76
+ if config.pyproject_path
77
+ venv_dir = File.join(File.dirname(config.pyproject_path.to_s), '.venv')
78
+ puts ".venv exists: #{Dir.exist?(venv_dir)}"
79
+ end
80
+
81
+ system_uv = `which uv 2>/dev/null`.strip
82
+ puts "System uv: #{!system_uv.empty? ? system_uv : '(not found)'}"
83
+ end
84
+
85
+ desc 'List installed Python packages'
86
+ task packages: :environment do
87
+ Rubyx::Rails.ensure_initialized!
88
+
89
+ gen = Rubyx.eval(<<~PY)
90
+ import pkg_resources
91
+ packages = sorted([f"{d.project_name}=={d.version}" for d in pkg_resources.working_set])
92
+ iter(packages)
93
+ PY
94
+
95
+ puts 'Installed Python packages:'
96
+ Rubyx.stream(gen).each { |pkg| puts " #{pkg}" }
97
+ rescue => e
98
+ begin
99
+ gen = Rubyx.eval(<<~PY)
100
+ from importlib.metadata import distributions
101
+ packages = sorted([f"{d.metadata['Name']}=={d.metadata['Version']}" for d in distributions()])
102
+ iter(packages)
103
+ PY
104
+
105
+ puts 'Installed Python packages:'
106
+ Rubyx.stream(gen).each { |pkg| puts " #{pkg}" }
107
+ rescue => e2
108
+ puts "Could not list packages: #{e2.message}"
109
+ end
110
+ end
111
+
112
+ desc 'Clear the Rubyx cache (re-download uv + Python on next init)'
113
+ task clear_cache: :environment do
114
+ cache_dir = File.join(
115
+ ENV.fetch('XDG_CACHE_HOME', File.join(Dir.home, '.cache')),
116
+ 'rubyx'
117
+ )
118
+
119
+ if Dir.exist?(cache_dir)
120
+ require 'fileutils'
121
+ FileUtils.rm_rf(cache_dir)
122
+ puts "[Rubyx] Cache cleared: #{cache_dir}"
123
+ else
124
+ puts '[Rubyx] No cache directory found.'
125
+ end
126
+ end
127
+ end
data/lib/rubyx/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Rubyx
3
- VERSION = "0.1.1".freeze
4
- end
3
+ VERSION = "0.2.1".freeze
4
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyx-py
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Naiker
@@ -62,6 +62,7 @@ extra_rdoc_files: []
62
62
  files:
63
63
  - Cargo.toml
64
64
  - README.md
65
+ - docs/assets/logo.png
65
66
  - ext/rubyx/Cargo.toml
66
67
  - ext/rubyx/extconf.rb
67
68
  - ext/rubyx/src/async_gen.rs
@@ -70,6 +71,7 @@ files:
70
71
  - ext/rubyx/src/eval.rs
71
72
  - ext/rubyx/src/exception.rs
72
73
  - ext/rubyx/src/future.rs
74
+ - ext/rubyx/src/gvl.rs
73
75
  - ext/rubyx/src/import.rs
74
76
  - ext/rubyx/src/lib.rs
75
77
  - ext/rubyx/src/nonblocking_stream.rs
@@ -94,6 +96,7 @@ files:
94
96
  - lib/rubyx/error.rb
95
97
  - lib/rubyx/rails.rb
96
98
  - lib/rubyx/railtie.rb
99
+ - lib/rubyx/tasks/rubyx.rake
97
100
  - lib/rubyx/uv.rb
98
101
  - lib/rubyx/version.rb
99
102
  homepage: https://github.com/yinho999/rubyx