rbcsv 0.1.8 → 0.2.0
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/CHANGELOG.md +23 -0
- data/Cargo.lock +1 -1
- data/DEVELOPMENT.md +121 -11
- data/README.md +89 -67
- data/docs/exe_upgrade_version.md +124 -0
- data/docs/release_process_v0.1.8.md +298 -0
- data/docs/special_character_bug_fix.md +257 -0
- data/docs/write_functionality_implementation.md +197 -0
- data/examples/README.md +221 -0
- data/{test.rb → examples/basic/basic_usage.rb} +2 -1
- data/{test_fixed.rb → examples/basic/test_fixed.rb} +1 -1
- data/examples/benchmarks/benchmark.rb +372 -0
- data/{output_comparison.rb → examples/benchmarks/output_comparison.rb} +41 -26
- data/examples/benchmarks/sample.csv +1001 -0
- data/examples/features/test_typed_functionality.rb +109 -0
- data/{test_write_functionality.rb → examples/features/test_write_functionality.rb} +1 -1
- data/ext/rbcsv/Cargo.toml +1 -1
- data/ext/rbcsv/src/error.rs +2 -2
- data/ext/rbcsv/src/lib.rs +8 -1
- data/ext/rbcsv/src/parser.rs +74 -15
- data/ext/rbcsv/src/ruby_api.rs +101 -2
- data/ext/rbcsv/src/value.rs +87 -0
- data/lib/rbcsv/version.rb +1 -1
- metadata +16 -8
- data/benchmark.rb +0 -190
- /data/{quick_test.rb → examples/basic/quick_test.rb} +0 -0
- /data/{test_install.rb → examples/basic/test_install.rb} +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# CSV書き込み機能実装ガイド
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
本ドキュメントは、RbCsv gemにおけるCSV書き込み機能(`RbCsv.write()`)の実装プロセスを詳細に記録したものです。実装から重要なバグ修正、そして最終的なリリースまでの一連の流れを説明しています。
|
|
6
|
+
|
|
7
|
+
## 実装目標
|
|
8
|
+
|
|
9
|
+
CSVデータを配列からファイルに書き込む機能を追加し、既存の読み込み機能との完全な互換性を確保する。
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# 目標API
|
|
13
|
+
data = [
|
|
14
|
+
['name', 'age', 'city'],
|
|
15
|
+
['Alice', '25', 'Tokyo'],
|
|
16
|
+
['Bob', '30', 'Osaka']
|
|
17
|
+
]
|
|
18
|
+
RbCsv.write('output.csv', data)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 実装アーキテクチャ
|
|
22
|
+
|
|
23
|
+
### 1. コア実装層(Rust)
|
|
24
|
+
|
|
25
|
+
#### `ext/rbcsv/src/parser.rs`
|
|
26
|
+
```rust
|
|
27
|
+
pub fn write_csv_file(file_path: &str, data: &[Vec<String>]) -> Result<(), CsvError>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**主要機能:**
|
|
31
|
+
- データ検証(空配列チェック、フィールド数整合性)
|
|
32
|
+
- ファイルパス検証(親ディレクトリ存在確認)
|
|
33
|
+
- CSV Writer設定とデータ書き込み
|
|
34
|
+
- エラーハンドリング(権限エラー、IOエラー)
|
|
35
|
+
|
|
36
|
+
#### `ext/rbcsv/src/ruby_api.rs`
|
|
37
|
+
```rust
|
|
38
|
+
pub fn write(ruby: &Ruby, file_path: String, data: Vec<Vec<String>>) -> Result<(), MagnusError>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**役割:**
|
|
42
|
+
- RubyとRust間のAPIブリッジ
|
|
43
|
+
- データ型変換(Ruby Array → Rust Vec)
|
|
44
|
+
- エラー変換(Rust Error → Ruby Exception)
|
|
45
|
+
|
|
46
|
+
#### `ext/rbcsv/src/error.rs`
|
|
47
|
+
```rust
|
|
48
|
+
// 新規追加エラータイプ
|
|
49
|
+
WritePermission, // 書き込み権限エラー
|
|
50
|
+
InvalidData, // 無効なデータエラー
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Ruby API層
|
|
54
|
+
|
|
55
|
+
#### `ext/rbcsv/src/lib.rs`
|
|
56
|
+
```rust
|
|
57
|
+
module.define_singleton_method("write", magnus::function!(write, 2))?;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Ruby側からの呼び出しを可能にするAPIメソッド登録。
|
|
61
|
+
|
|
62
|
+
## データ検証ロジック
|
|
63
|
+
|
|
64
|
+
### 1. 空データチェック
|
|
65
|
+
```rust
|
|
66
|
+
if data.is_empty() {
|
|
67
|
+
return Err(CsvError::invalid_data("CSV data is empty"));
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 2. フィールド数整合性チェック
|
|
72
|
+
```rust
|
|
73
|
+
let expected_len = data[0].len();
|
|
74
|
+
for (line_num, row) in data.iter().enumerate() {
|
|
75
|
+
if row.len() != expected_len {
|
|
76
|
+
let error_msg = format!(
|
|
77
|
+
"Field count mismatch at line {}: expected {} fields, got {} fields",
|
|
78
|
+
line_num + 1, expected_len, row.len()
|
|
79
|
+
);
|
|
80
|
+
return Err(CsvError::invalid_data(error_msg));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 3. ファイルパス検証
|
|
86
|
+
```rust
|
|
87
|
+
let path = Path::new(file_path);
|
|
88
|
+
if let Some(parent) = path.parent() {
|
|
89
|
+
if !parent.exists() {
|
|
90
|
+
return Err(CsvError::io(format!("Parent directory does not exist: {}", parent.display())));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## エラーハンドリング戦略
|
|
96
|
+
|
|
97
|
+
### 1. 権限エラー
|
|
98
|
+
```rust
|
|
99
|
+
if e.kind() == std::io::ErrorKind::PermissionDenied {
|
|
100
|
+
return Err(CsvError::write_permission(format!("Permission denied: {}", file_path)));
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. データ検証エラー
|
|
105
|
+
- 空データ:`CSV data is empty`
|
|
106
|
+
- フィールド数不一致:`Field count mismatch at line X: expected Y fields, got Z fields`
|
|
107
|
+
|
|
108
|
+
### 3. IOエラー
|
|
109
|
+
- ファイル作成失敗:`Failed to create file 'path': error`
|
|
110
|
+
- フラッシュ失敗:`Failed to flush data to file 'path': error`
|
|
111
|
+
|
|
112
|
+
## テスト戦略
|
|
113
|
+
|
|
114
|
+
### 1. Rust単体テスト(`ext/rbcsv/src/parser.rs`)
|
|
115
|
+
```rust
|
|
116
|
+
#[test]
|
|
117
|
+
fn test_write_csv_file_basic() { /* 基本書き込みテスト */ }
|
|
118
|
+
|
|
119
|
+
#[test]
|
|
120
|
+
fn test_write_csv_file_empty_data() { /* 空データエラーテスト */ }
|
|
121
|
+
|
|
122
|
+
#[test]
|
|
123
|
+
fn test_write_csv_file_field_count_mismatch() { /* フィールド数不一致テスト */ }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 2. RSpec統合テスト(`spec/rbcsv_spec.rb`)
|
|
127
|
+
```ruby
|
|
128
|
+
describe ".write" do
|
|
129
|
+
it "writes CSV data to file"
|
|
130
|
+
it "overwrites existing file"
|
|
131
|
+
it "raises error for empty data"
|
|
132
|
+
it "raises error for inconsistent field count"
|
|
133
|
+
it "can write and read back the same data"
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. 実行可能テストスクリプト(`test_write_functionality.rb`)
|
|
138
|
+
8つの包括的テストケース:
|
|
139
|
+
- 基本的なCSV書き込み
|
|
140
|
+
- 往復テスト(write → read)
|
|
141
|
+
- ファイル上書き
|
|
142
|
+
- エラーハンドリング(空データ、フィールド数不一致)
|
|
143
|
+
- Unicode文字対応
|
|
144
|
+
- 特殊文字処理
|
|
145
|
+
|
|
146
|
+
## CSV標準準拠
|
|
147
|
+
|
|
148
|
+
### RFC 4180準拠
|
|
149
|
+
- カンマ区切り
|
|
150
|
+
- ダブルクォート内のダブルクォートエスケープ(`"""`)
|
|
151
|
+
- 改行文字の適切な処理
|
|
152
|
+
- フィールド内のカンマ、改行の適切なクォート
|
|
153
|
+
|
|
154
|
+
### csv crateの活用
|
|
155
|
+
```rust
|
|
156
|
+
let mut writer = csv::WriterBuilder::new()
|
|
157
|
+
.has_headers(false)
|
|
158
|
+
.from_writer(file);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Rustの`csv` crateを使用することで、標準準拠のCSV出力を保証。
|
|
162
|
+
|
|
163
|
+
## 実装完了基準
|
|
164
|
+
|
|
165
|
+
1. ✅ **機能実装**: write機能の完全実装
|
|
166
|
+
2. ✅ **データ検証**: 包括的な入力データ検証
|
|
167
|
+
3. ✅ **エラーハンドリング**: 詳細なエラーメッセージ
|
|
168
|
+
4. ✅ **テストカバレッジ**: Rust + Ruby + 実行可能テスト
|
|
169
|
+
5. ✅ **ドキュメント**: DEVELOPMENT.mdの更新
|
|
170
|
+
6. ✅ **往復互換性**: write/readの完全互換性
|
|
171
|
+
|
|
172
|
+
## パフォーマンス考慮事項
|
|
173
|
+
|
|
174
|
+
### 1. メモリ効率
|
|
175
|
+
- ストリーミング書き込み(大容量データ対応)
|
|
176
|
+
- 不要なデータコピーの回避
|
|
177
|
+
|
|
178
|
+
### 2. エラー早期検出
|
|
179
|
+
- データ検証を書き込み前に実行
|
|
180
|
+
- 失敗時のリソース無駄遣い防止
|
|
181
|
+
|
|
182
|
+
### 3. ファイルハンドリング
|
|
183
|
+
- 適切なファイルフラッシュ
|
|
184
|
+
- エラー時のファイル状態管理
|
|
185
|
+
|
|
186
|
+
## 次のステップ
|
|
187
|
+
|
|
188
|
+
1. **パフォーマンステスト**: 大容量データでのベンチマーク
|
|
189
|
+
2. **設定オプション追加**: 区切り文字カスタマイズ等
|
|
190
|
+
3. **ストリーミングAPI**: メモリ効率の更なる向上
|
|
191
|
+
4. **非同期書き込み**: 大容量データの非同期処理
|
|
192
|
+
|
|
193
|
+
## 参考資料
|
|
194
|
+
|
|
195
|
+
- [RFC 4180 - CSV形式仕様](https://tools.ietf.org/html/rfc4180)
|
|
196
|
+
- [Rust csv crate documentation](https://docs.rs/csv/)
|
|
197
|
+
- [Magnus framework documentation](https://docs.rs/magnus/)
|
data/examples/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# RbCsv Examples
|
|
2
|
+
|
|
3
|
+
このディレクトリには、RbCsvライブラリの使用方法を示すサンプルコードとテストスクリプトが含まれています。
|
|
4
|
+
|
|
5
|
+
## ディレクトリ構成
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
examples/
|
|
9
|
+
├── basic/ # 基本的な使用例
|
|
10
|
+
├── features/ # 特定機能のテスト・デモ
|
|
11
|
+
├── benchmarks/ # パフォーマンステストとベンチマーク
|
|
12
|
+
└── README.md # このファイル
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 実行方法
|
|
16
|
+
|
|
17
|
+
全ての例を実行する前に、ライブラリがビルドされていることを確認してください:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# プロジェクトルートで実行
|
|
21
|
+
bundle exec rake compile
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 📁 basic/ - 基本的な使用例
|
|
25
|
+
|
|
26
|
+
### `basic_usage.rb`
|
|
27
|
+
RbCsvの基本的な機能(parse, read, write)の使用例です。
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd examples/basic
|
|
31
|
+
ruby basic_usage.rb
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**機能:**
|
|
35
|
+
- CSV文字列のパース
|
|
36
|
+
- CSVファイルの読み込み
|
|
37
|
+
- CSVファイルの書き込み
|
|
38
|
+
|
|
39
|
+
### `quick_test.rb`
|
|
40
|
+
簡単な動作確認用スクリプトです。
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cd examples/basic
|
|
44
|
+
ruby quick_test.rb
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### `test_fixed.rb`
|
|
48
|
+
特定のバグ修正後の動作確認用スクリプトです。
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd examples/basic
|
|
52
|
+
ruby test_fixed.rb
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `test_install.rb`
|
|
56
|
+
インストール後の動作確認用スクリプトです。
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
cd examples/basic
|
|
60
|
+
ruby test_install.rb
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 🔧 features/ - 特定機能のテスト・デモ
|
|
64
|
+
|
|
65
|
+
### `test_typed_functionality.rb`
|
|
66
|
+
型認識機能(parse_typed, read_typed系)の詳細なテストとデモです。
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cd examples/features
|
|
70
|
+
ruby test_typed_functionality.rb
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**機能:**
|
|
74
|
+
- `parse_typed()`: 数値文字列を数値型に自動変換
|
|
75
|
+
- `parse_typed!()`: trim + 型変換
|
|
76
|
+
- `read_typed()`: ファイル読み込み + 型変換
|
|
77
|
+
- `read_typed!()`: ファイル読み込み + trim + 型変換
|
|
78
|
+
|
|
79
|
+
**型変換例:**
|
|
80
|
+
- `"123"` → `123` (Integer)
|
|
81
|
+
- `"45.6"` → `45.6` (Float)
|
|
82
|
+
- `"1.23e-4"` → `0.000123` (Float)
|
|
83
|
+
- `"hello"` → `"hello"` (String)
|
|
84
|
+
|
|
85
|
+
### `test_write_functionality.rb`
|
|
86
|
+
CSV書き込み機能の包括的なテストです。
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cd examples/features
|
|
90
|
+
ruby test_write_functionality.rb
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**機能:**
|
|
94
|
+
- 基本的なCSV書き込み
|
|
95
|
+
- ファイル上書き動作
|
|
96
|
+
- エラーハンドリング(空データ、フィールド数不一致など)
|
|
97
|
+
- 書き込み→読み込みの往復テスト
|
|
98
|
+
|
|
99
|
+
## 📊 benchmarks/ - パフォーマンステスト
|
|
100
|
+
|
|
101
|
+
### `benchmark.rb`
|
|
102
|
+
標準ライブラリのCSVとRbCsvの包括的なパフォーマンス比較です。
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
cd examples/benchmarks
|
|
106
|
+
ruby benchmark.rb
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**自動機能:**
|
|
110
|
+
- サンプルCSVファイルを自動生成(1000レコード)
|
|
111
|
+
- 大容量テストデータを自動作成(50,000レコード)
|
|
112
|
+
- ベンチマーク後の自動クリーンアップ
|
|
113
|
+
|
|
114
|
+
**測定項目:**
|
|
115
|
+
- **基本パース性能**: `parse` vs `parse_typed` vs Ruby CSV
|
|
116
|
+
- **ファイル読み込み性能**: `read` vs `read_typed` vs Ruby CSV
|
|
117
|
+
- **大容量データ処理**: 50,000レコードでの性能比較
|
|
118
|
+
- **メモリ使用量**: パース処理でのメモリ効率性
|
|
119
|
+
- **データ処理性能**: フィルタリングと検索の速度比較
|
|
120
|
+
- **型変換比較**: 手動変換 vs 自動型認識の性能差
|
|
121
|
+
- **データ精度検証**: 結果の正確性確認
|
|
122
|
+
|
|
123
|
+
**期待される結果:**
|
|
124
|
+
- RbCsv は Ruby標準CSV より 2-4倍 高速
|
|
125
|
+
- `parse_typed` は型変換コストを大幅削減
|
|
126
|
+
- メモリ使用量も効率的
|
|
127
|
+
|
|
128
|
+
### `output_comparison.rb`
|
|
129
|
+
標準ライブラリのCSVとRbCsvの出力形式比較です。
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
cd examples/benchmarks
|
|
133
|
+
ruby output_comparison.rb
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**要求事項:**
|
|
137
|
+
- `sample.csv` ファイルが必要
|
|
138
|
+
|
|
139
|
+
## 🚀 実行例
|
|
140
|
+
|
|
141
|
+
### 基本的なCSV処理
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
require_relative '../../lib/rbcsv'
|
|
145
|
+
|
|
146
|
+
# CSV文字列のパース
|
|
147
|
+
csv_data = "name,age,city\nAlice,25,Tokyo\nBob,30,Osaka"
|
|
148
|
+
result = RbCsv.parse(csv_data)
|
|
149
|
+
# => [["name", "age", "city"], ["Alice", "25", "Tokyo"], ["Bob", "30", "Osaka"]]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 型認識パース
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
require_relative '../../lib/rbcsv'
|
|
156
|
+
|
|
157
|
+
# 数値を自動的に数値型に変換
|
|
158
|
+
csv_data = "name,age,score\nAlice,25,85.5\nBob,30,92"
|
|
159
|
+
result = RbCsv.parse_typed(csv_data)
|
|
160
|
+
# => [["name", "age", "score"], ["Alice", 25, 85.5], ["Bob", 30, 92]]
|
|
161
|
+
# 注意: 25は整数、85.5は浮動小数点数として返される
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### CSVファイルの書き込みと読み込み
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
require_relative '../../lib/rbcsv'
|
|
168
|
+
|
|
169
|
+
# データの準備
|
|
170
|
+
data = [
|
|
171
|
+
["product", "price", "quantity"],
|
|
172
|
+
["Apple", "100", "50"],
|
|
173
|
+
["Orange", "80.5", "30"]
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
# ファイルに書き込み
|
|
177
|
+
RbCsv.write("output.csv", data)
|
|
178
|
+
|
|
179
|
+
# ファイルから読み込み(型認識付き)
|
|
180
|
+
result = RbCsv.read_typed("output.csv")
|
|
181
|
+
# => [["product", "price", "quantity"], ["Apple", 100, 50], ["Orange", 80.5, 30]]
|
|
182
|
+
# 注意: price と quantity が数値型として読み込まれる
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 💡 トラブルシューティング
|
|
186
|
+
|
|
187
|
+
### よくあるエラー
|
|
188
|
+
|
|
189
|
+
**1. LoadError: cannot load such file**
|
|
190
|
+
```bash
|
|
191
|
+
# 解決方法: ライブラリをビルドしてください
|
|
192
|
+
bundle exec rake compile
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**2. RuntimeError: Invalid Data Error**
|
|
196
|
+
```bash
|
|
197
|
+
# 原因: CSVデータの形式が不正
|
|
198
|
+
# 解決方法: データの整合性を確認してください(空データ、フィールド数不一致など)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**3. 相対パスエラー**
|
|
202
|
+
```bash
|
|
203
|
+
# 解決方法: examples/ディレクトリのサブディレクトリから実行してください
|
|
204
|
+
cd examples/basic
|
|
205
|
+
ruby basic_usage.rb
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## 📝 新しい例の追加
|
|
209
|
+
|
|
210
|
+
新しい例を追加する場合:
|
|
211
|
+
|
|
212
|
+
1. 適切なディレクトリを選択(basic, features, benchmarks)
|
|
213
|
+
2. `require_relative '../../lib/rbcsv'` を使用
|
|
214
|
+
3. スクリプトの先頭にコメントで目的と使用方法を記述
|
|
215
|
+
4. このREADMEを更新
|
|
216
|
+
|
|
217
|
+
## 🔗 関連ドキュメント
|
|
218
|
+
|
|
219
|
+
- [メインREADME](../README.md) - 基本的な使用方法
|
|
220
|
+
- [開発ガイド](../DEVELOPMENT.md) - 開発者向け詳細情報
|
|
221
|
+
- [RSpecテスト](../spec/) - 自動テストスイート
|