rbcsv 0.1.6 → 0.1.8

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.
@@ -0,0 +1,111 @@
1
+ use magnus::{Error as MagnusError, Ruby};
2
+ use crate::parser::{parse_csv_core, parse_csv_file, write_csv_file};
3
+
4
+ /// CSV文字列をパースする(通常版)
5
+ ///
6
+ /// # Arguments
7
+ /// * `ruby` - Ruby VMの参照
8
+ /// * `s` - パースするCSV文字列
9
+ ///
10
+ /// # Returns
11
+ /// * `Result<Vec<Vec<String>>, MagnusError>` - パース結果またはエラー
12
+ pub fn parse(ruby: &Ruby, s: String) -> Result<Vec<Vec<String>>, MagnusError> {
13
+ parse_csv_core(&s, csv::Trim::None)
14
+ .map_err(|e| MagnusError::new(ruby.exception_runtime_error(), e.to_string()))
15
+ }
16
+
17
+ /// CSV文字列をパースする(trim版)
18
+ ///
19
+ /// # Arguments
20
+ /// * `ruby` - Ruby VMの参照
21
+ /// * `s` - パースするCSV文字列
22
+ ///
23
+ /// # Returns
24
+ /// * `Result<Vec<Vec<String>>, MagnusError>` - パース結果またはエラー
25
+ pub fn parse_trim(ruby: &Ruby, s: String) -> Result<Vec<Vec<String>>, MagnusError> {
26
+ parse_csv_core(&s, csv::Trim::All)
27
+ .map_err(|e| MagnusError::new(ruby.exception_runtime_error(), e.to_string()))
28
+ }
29
+
30
+ /// CSVファイルを読み込む(通常版)
31
+ ///
32
+ /// # Arguments
33
+ /// * `ruby` - Ruby VMの参照
34
+ /// * `file_path` - 読み込むCSVファイルのパス
35
+ ///
36
+ /// # Returns
37
+ /// * `Result<Vec<Vec<String>>, MagnusError>` - パース結果またはエラー
38
+ pub fn read(ruby: &Ruby, file_path: String) -> Result<Vec<Vec<String>>, MagnusError> {
39
+ parse_csv_file(&file_path, csv::Trim::None)
40
+ .map_err(|e| MagnusError::new(ruby.exception_runtime_error(), e.to_string()))
41
+ }
42
+
43
+ /// CSVファイルを読み込む(trim版)
44
+ ///
45
+ /// # Arguments
46
+ /// * `ruby` - Ruby VMの参照
47
+ /// * `file_path` - 読み込むCSVファイルのパス
48
+ ///
49
+ /// # Returns
50
+ /// * `Result<Vec<Vec<String>>, MagnusError>` - パース結果またはエラー
51
+ pub fn read_trim(ruby: &Ruby, file_path: String) -> Result<Vec<Vec<String>>, MagnusError> {
52
+ parse_csv_file(&file_path, csv::Trim::All)
53
+ .map_err(|e| MagnusError::new(ruby.exception_runtime_error(), e.to_string()))
54
+ }
55
+
56
+ /// CSVファイルに書き込む
57
+ ///
58
+ /// # Arguments
59
+ /// * `ruby` - Ruby VMの参照
60
+ /// * `file_path` - 書き込み先ファイルのパス
61
+ /// * `data` - 書き込むCSVデータ(2次元配列)
62
+ ///
63
+ /// # Returns
64
+ /// * `Result<(), MagnusError>` - 成功時は空、失敗時はエラー
65
+ pub fn write(ruby: &Ruby, file_path: String, data: Vec<Vec<String>>) -> Result<(), MagnusError> {
66
+ write_csv_file(&file_path, &data)
67
+ .map_err(|e| MagnusError::new(ruby.exception_runtime_error(), e.to_string()))
68
+ }
69
+
70
+
71
+ #[cfg(test)]
72
+ mod tests {
73
+
74
+ #[test]
75
+ fn test_parse_basic() {
76
+ let csv_data = "a,b,c\n1,2,3";
77
+ let result = crate::parser::parse_csv_core(csv_data, csv::Trim::None);
78
+
79
+ assert!(result.is_ok());
80
+ let records = result.unwrap();
81
+ assert_eq!(records.len(), 2);
82
+ assert_eq!(records[0], vec!["a", "b", "c"]);
83
+ assert_eq!(records[1], vec!["1", "2", "3"]);
84
+ }
85
+
86
+ #[test]
87
+ fn test_parse_with_trim_enabled() {
88
+ let csv_data = " a , b , c \n 1 , 2 , 3 ";
89
+ let result = crate::parser::parse_csv_core(csv_data, csv::Trim::All);
90
+
91
+ assert!(result.is_ok());
92
+ let records = result.unwrap();
93
+ assert_eq!(records[0], vec!["a", "b", "c"]);
94
+ assert_eq!(records[1], vec!["1", "2", "3"]);
95
+ }
96
+
97
+ #[test]
98
+ fn test_parse_with_trim_disabled() {
99
+ let csv_data = " a , b , c \n 1 , 2 , 3 ";
100
+ let result = crate::parser::parse_csv_core(csv_data, csv::Trim::None);
101
+
102
+ assert!(result.is_ok());
103
+ let records = result.unwrap();
104
+ assert_eq!(records[0], vec![" a ", " b ", " c "]);
105
+ assert_eq!(records[1], vec![" 1 ", " 2 ", " 3 "]);
106
+ }
107
+
108
+ // Note: Ruby API functions that return MagnusError cannot be tested
109
+ // in unit tests because they require a Ruby VM context.
110
+ // File reading functionality is tested in the parser module.
111
+ }
data/lib/rbcsv/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RbCsv
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.8"
5
5
  end
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # RbCsv.write() 機能のテストスクリプト
5
+ #
6
+ # 実行方法:
7
+ # ruby test_write_functionality.rb
8
+ #
9
+ # 前提条件:
10
+ # - bundle exec rake compile でライブラリがビルド済みであること
11
+
12
+ require_relative 'lib/rbcsv'
13
+ require 'fileutils'
14
+
15
+ class RbCsvWriteTest
16
+ def initialize
17
+ @test_dir = '/tmp/rbcsv_write_tests'
18
+ @success_count = 0
19
+ @total_count = 0
20
+ setup_test_directory
21
+ end
22
+
23
+ def run_all_tests
24
+ puts "=" * 60
25
+ puts "RbCsv.write() 機能テスト開始"
26
+ puts "=" * 60
27
+ puts
28
+
29
+ test_basic_write
30
+ test_roundtrip_write_read
31
+ test_file_overwrite
32
+ test_empty_data_error
33
+ test_field_count_mismatch_error
34
+ test_single_row_write
35
+ test_unicode_content
36
+ test_special_characters
37
+
38
+ puts
39
+ puts "=" * 60
40
+ puts "テスト結果: #{@success_count}/#{@total_count} 成功"
41
+ puts "=" * 60
42
+
43
+ cleanup_test_directory
44
+
45
+ if @success_count == @total_count
46
+ puts "✅ すべてのテストが成功しました!"
47
+ exit 0
48
+ else
49
+ puts "❌ 一部のテストが失敗しました"
50
+ exit 1
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def setup_test_directory
57
+ FileUtils.mkdir_p(@test_dir)
58
+ puts "テストディレクトリを作成: #{@test_dir}"
59
+ end
60
+
61
+ def cleanup_test_directory
62
+ FileUtils.rm_rf(@test_dir)
63
+ puts "テストディレクトリを削除: #{@test_dir}"
64
+ end
65
+
66
+ def run_test(name)
67
+ @total_count += 1
68
+ print "#{name}... "
69
+
70
+ begin
71
+ yield
72
+ @success_count += 1
73
+ puts "✅ 成功"
74
+ rescue => e
75
+ puts "❌ 失敗: #{e.message}"
76
+ puts " #{e.backtrace.first}"
77
+ end
78
+ end
79
+
80
+ def test_basic_write
81
+ run_test("基本的なCSV書き込み") do
82
+ file_path = File.join(@test_dir, 'basic.csv')
83
+ data = [
84
+ ['name', 'age', 'city'],
85
+ ['Alice', '25', 'Tokyo'],
86
+ ['Bob', '30', 'Osaka']
87
+ ]
88
+
89
+ RbCsv.write(file_path, data)
90
+
91
+ content = File.read(file_path)
92
+ expected = "name,age,city\nAlice,25,Tokyo\nBob,30,Osaka\n"
93
+
94
+ raise "書き込み内容が期待値と異なります" unless content == expected
95
+ end
96
+ end
97
+
98
+ def test_roundtrip_write_read
99
+ run_test("書き込み→読み込みの往復テスト") do
100
+ file_path = File.join(@test_dir, 'roundtrip.csv')
101
+ original_data = [
102
+ ['product', 'price', 'category'],
103
+ ['Apple', '100', 'Fruit'],
104
+ ['Carrot', '50', 'Vegetable']
105
+ ]
106
+
107
+ RbCsv.write(file_path, original_data)
108
+ read_data = RbCsv.read(file_path)
109
+
110
+ raise "往復テストで元データと異なります" unless original_data == read_data
111
+ end
112
+ end
113
+
114
+ def test_file_overwrite
115
+ run_test("ファイル上書きテスト") do
116
+ file_path = File.join(@test_dir, 'overwrite.csv')
117
+
118
+ # 最初のデータ
119
+ first_data = [['old'], ['data']]
120
+ RbCsv.write(file_path, first_data)
121
+ first_content = File.read(file_path)
122
+
123
+ # 上書き
124
+ second_data = [['new', 'header'], ['updated', 'content']]
125
+ RbCsv.write(file_path, second_data)
126
+ second_content = File.read(file_path)
127
+
128
+ expected_first = "old\ndata\n"
129
+ expected_second = "new,header\nupdated,content\n"
130
+
131
+ raise "最初の書き込み内容が不正" unless first_content == expected_first
132
+ raise "上書き後の内容が不正" unless second_content == expected_second
133
+ end
134
+ end
135
+
136
+ def test_empty_data_error
137
+ run_test("空データエラーテスト") do
138
+ file_path = File.join(@test_dir, 'empty.csv')
139
+
140
+ error_raised = false
141
+ begin
142
+ RbCsv.write(file_path, [])
143
+ rescue RuntimeError => e
144
+ error_raised = true
145
+ raise "エラーメッセージが期待値と異なります" unless e.message.include?("CSV data is empty")
146
+ end
147
+
148
+ raise "空データでエラーが発生しませんでした" unless error_raised
149
+ end
150
+ end
151
+
152
+ def test_field_count_mismatch_error
153
+ run_test("フィールド数不一致エラーテスト") do
154
+ file_path = File.join(@test_dir, 'mismatch.csv')
155
+ inconsistent_data = [
156
+ ['name', 'age'],
157
+ ['Alice', '25', 'Tokyo'] # 3フィールド(期待は2フィールド)
158
+ ]
159
+
160
+ error_raised = false
161
+ begin
162
+ RbCsv.write(file_path, inconsistent_data)
163
+ rescue RuntimeError => e
164
+ error_raised = true
165
+ unless e.message.include?("Field count mismatch") && e.message.include?("line 2")
166
+ raise "エラーメッセージが期待値と異なります: #{e.message}"
167
+ end
168
+ end
169
+
170
+ raise "フィールド数不一致でエラーが発生しませんでした" unless error_raised
171
+ end
172
+ end
173
+
174
+ def test_single_row_write
175
+ run_test("単一行書き込みテスト") do
176
+ file_path = File.join(@test_dir, 'single.csv')
177
+ data = [['single', 'row', 'test']]
178
+
179
+ RbCsv.write(file_path, data)
180
+ content = File.read(file_path)
181
+ expected = "single,row,test\n"
182
+
183
+ raise "単一行の書き込み内容が不正" unless content == expected
184
+ end
185
+ end
186
+
187
+ def test_unicode_content
188
+ run_test("Unicode文字テスト") do
189
+ file_path = File.join(@test_dir, 'unicode.csv')
190
+ data = [
191
+ ['名前', '年齢', '都市'],
192
+ ['田中太郎', '30', '東京'],
193
+ ['山田花子', '25', '大阪'],
194
+ ['🎉', '😀', '🌸']
195
+ ]
196
+
197
+ RbCsv.write(file_path, data)
198
+ read_data = RbCsv.read(file_path)
199
+
200
+ raise "Unicode文字の往復テストが失敗" unless data == read_data
201
+ end
202
+ end
203
+
204
+ def test_special_characters
205
+ run_test("特殊文字テスト") do
206
+ file_path = File.join(@test_dir, 'special.csv')
207
+ data = [
208
+ ['field1', 'field2', 'field3'],
209
+ ['comma,test', 'quote"test', 'newline\ntest'],
210
+ ['tab\ttest', 'backslash\\test', 'normal']
211
+ ]
212
+
213
+ RbCsv.write(file_path, data)
214
+ read_data = RbCsv.read(file_path)
215
+
216
+ raise "特殊文字の往復テストが失敗" unless data == read_data
217
+ end
218
+ end
219
+ end
220
+
221
+ # テスト実行
222
+ if __FILE__ == $0
223
+ tester = RbCsvWriteTest.new
224
+ tester.run_all_tests
225
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rbcsv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - fujitani sora
@@ -33,6 +33,7 @@ extensions:
33
33
  extra_rdoc_files: []
34
34
  files:
35
35
  - ".ruby-version"
36
+ - ".rust-analyzer.json"
36
37
  - ".serena/.gitignore"
37
38
  - ".serena/memories/code_style_conventions.md"
38
39
  - ".serena/memories/project_overview.md"
@@ -50,7 +51,10 @@ files:
50
51
  - benchmark.rb
51
52
  - ext/rbcsv/Cargo.toml
52
53
  - ext/rbcsv/extconf.rb
54
+ - ext/rbcsv/src/error.rs
53
55
  - ext/rbcsv/src/lib.rs
56
+ - ext/rbcsv/src/parser.rs
57
+ - ext/rbcsv/src/ruby_api.rs
54
58
  - lib/rbcsv.rb
55
59
  - lib/rbcsv/version.rb
56
60
  - output_comparison.rb
@@ -60,6 +64,7 @@ files:
60
64
  - test.rb
61
65
  - test_fixed.rb
62
66
  - test_install.rb
67
+ - test_write_functionality.rb
63
68
  homepage: https://github.com/fs0414/rbcsv
64
69
  licenses:
65
70
  - MIT