rbcsv 0.1.4 → 0.1.7
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/.rust-analyzer.json +8 -0
- data/CHANGELOG.md +50 -0
- data/Cargo.lock +123 -44
- data/DEVELOPMENT.md +491 -0
- data/ext/rbcsv/Cargo.toml +6 -1
- data/ext/rbcsv/src/error.rs +87 -0
- data/ext/rbcsv/src/lib.rs +12 -62
- data/ext/rbcsv/src/parser.rs +187 -0
- data/ext/rbcsv/src/ruby_api.rs +98 -0
- data/lib/rbcsv/version.rb +1 -1
- metadata +7 -2
data/DEVELOPMENT.md
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# RbCsv 開発ガイド
|
|
2
|
+
|
|
3
|
+
このドキュメントでは、rbcsvの開発環境のセットアップ、ビルド方法、テスト手順、リリース手順について詳しく説明します。
|
|
4
|
+
|
|
5
|
+
## 必要な環境
|
|
6
|
+
|
|
7
|
+
- **Ruby**: 3.2.0以降(gemspec要件)
|
|
8
|
+
- **Rust**: 最新の安定版を推奨(MSRV: 1.75+)
|
|
9
|
+
- **Bundler**: gem管理
|
|
10
|
+
- **Git**: バージョン管理
|
|
11
|
+
- **RubyGems**: 3.3.11以降
|
|
12
|
+
|
|
13
|
+
### システム要件
|
|
14
|
+
|
|
15
|
+
- **macOS**: Apple Silicon (arm64) / Intel (x86_64)
|
|
16
|
+
- **Linux**: x86_64 / aarch64
|
|
17
|
+
- **Windows**: x86_64(実験的サポート)
|
|
18
|
+
|
|
19
|
+
## プロジェクト構成
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
r_csv/
|
|
23
|
+
├── lib/
|
|
24
|
+
│ ├── rbcsv.rb # メインのRubyエントリーポイント
|
|
25
|
+
│ └── rbcsv/
|
|
26
|
+
│ ├── version.rb # バージョン定義
|
|
27
|
+
│ └── rbcsv.bundle # コンパイル済みネイティブ拡張(生成される)
|
|
28
|
+
├── ext/
|
|
29
|
+
│ └── rbcsv/
|
|
30
|
+
│ ├── src/
|
|
31
|
+
│ │ ├── lib.rs # Rust拡張のエントリーポイント、Magnus初期化
|
|
32
|
+
│ │ ├── parser.rs # CSV解析コア、CsvParseOptions定義
|
|
33
|
+
│ │ ├── ruby_api.rs # Ruby APIバインディング、オプション処理
|
|
34
|
+
│ │ └── error.rs # エラーハンドリング
|
|
35
|
+
│ ├── Cargo.toml # Rust依存関係(Magnus 0.8.1使用)
|
|
36
|
+
│ └── extconf.rb # Ruby拡張ビルド設定
|
|
37
|
+
├── spec/
|
|
38
|
+
│ ├── rbcsv_spec.rb # メインのRubyテスト
|
|
39
|
+
│ └── spec_helper.rb # テスト設定
|
|
40
|
+
├── docs/ # ドキュメント
|
|
41
|
+
├── target/ # Rustビルド出力(git無視)
|
|
42
|
+
├── tmp/ # Ruby拡張ビルド中間ファイル(git無視)
|
|
43
|
+
├── *.gem # ビルド済みgemファイル
|
|
44
|
+
├── rbcsv.gemspec # Gem仕様
|
|
45
|
+
├── Rakefile # ビルドタスク(rb_sys使用)
|
|
46
|
+
├── Gemfile # Ruby依存関係
|
|
47
|
+
├── Cargo.toml # ワークスペース設定
|
|
48
|
+
├── CHANGELOG.md # 変更履歴
|
|
49
|
+
├── README.md # 使用法ガイド
|
|
50
|
+
└── DEVELOPMENT.md # このファイル
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 開発環境のセットアップ
|
|
54
|
+
|
|
55
|
+
### 1. リポジトリのクローン
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
git clone https://github.com/fs0414/rbcsv.git
|
|
59
|
+
cd rbcsv
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Ruby依存関係のインストール
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bundle install
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. ネイティブ拡張のビルド
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# 推奨方法(rb_sys使用)
|
|
72
|
+
rake compile
|
|
73
|
+
|
|
74
|
+
# 代替方法(開発時)
|
|
75
|
+
bundle exec rake compile
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. 動作確認
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# 基本的な動作確認
|
|
82
|
+
ruby -I lib -e "require 'rbcsv'; p RbCsv.parse('a,b\n1,2', {})"
|
|
83
|
+
# 期待される出力: [["a", "b"], ["1", "2"]]
|
|
84
|
+
|
|
85
|
+
# オプション付きテスト
|
|
86
|
+
ruby -I lib -e "require 'rbcsv'; p RbCsv.parse(' a , b \n 1 , 2 ', {trim: true})"
|
|
87
|
+
# 期待される出力: [["a", "b"], ["1", "2"]]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## ビルドプロセス
|
|
91
|
+
|
|
92
|
+
### 自動ビルド(推奨)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 全体ビルド(コンパイル、テスト、リント)
|
|
96
|
+
rake
|
|
97
|
+
|
|
98
|
+
# 拡張のみコンパイル
|
|
99
|
+
rake compile
|
|
100
|
+
|
|
101
|
+
# クリーンビルド
|
|
102
|
+
rake clean
|
|
103
|
+
rake compile
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 手動ビルド手順
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# 1. 前回のビルドをクリーン
|
|
110
|
+
rm -rf lib/rbcsv/rbcsv.bundle tmp/ target/
|
|
111
|
+
|
|
112
|
+
# 2. Rust拡張のコンパイル
|
|
113
|
+
cd ext/rbcsv
|
|
114
|
+
cargo build --release
|
|
115
|
+
cd ../..
|
|
116
|
+
|
|
117
|
+
# 3. バンドルファイルのコピー(macOSの場合)
|
|
118
|
+
cp target/release/librbcsv.dylib lib/rbcsv/rbcsv.bundle
|
|
119
|
+
|
|
120
|
+
# Linuxの場合
|
|
121
|
+
# cp target/release/librbcsv.so lib/rbcsv/rbcsv.bundle
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### ビルドのトラブルシューティング
|
|
125
|
+
|
|
126
|
+
#### ABIバージョンの不一致
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# エラー例: "incompatible ABI version"
|
|
130
|
+
rm -rf lib/rbcsv/rbcsv.bundle tmp/ target/
|
|
131
|
+
bundle exec rake compile
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Rust/Cargoの問題
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Rust依存関係の更新
|
|
138
|
+
cd ext/rbcsv
|
|
139
|
+
cargo update
|
|
140
|
+
cargo clean
|
|
141
|
+
cargo build --release
|
|
142
|
+
cd ../..
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### Magnus APIエラー
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# 最新のMagnus 0.8.1では、ReprValueトレイトの明示的インポートが必要
|
|
149
|
+
# ruby_api.rs で以下が含まれていることを確認:
|
|
150
|
+
use magnus::{value::ReprValue};
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## テスト手順
|
|
154
|
+
|
|
155
|
+
### Ruby統合テスト
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# 全テスト実行
|
|
159
|
+
bundle exec rspec
|
|
160
|
+
|
|
161
|
+
# 特定のテストファイル
|
|
162
|
+
bundle exec rspec spec/rbcsv_spec.rb
|
|
163
|
+
|
|
164
|
+
# 詳細出力
|
|
165
|
+
bundle exec rspec --format documentation
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Rustユニットテスト
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
# 全Rustテスト
|
|
172
|
+
cd ext/rbcsv
|
|
173
|
+
cargo test
|
|
174
|
+
|
|
175
|
+
# 詳細出力
|
|
176
|
+
cargo test -- --nocapture
|
|
177
|
+
|
|
178
|
+
# 特定のテスト
|
|
179
|
+
cargo test test_parse_basic
|
|
180
|
+
cd ../..
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### パフォーマンステスト
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# ベンチマーク実行
|
|
187
|
+
ruby benchmark.rb
|
|
188
|
+
|
|
189
|
+
# カスタムテストファイルでのテスト
|
|
190
|
+
ruby test.rb
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### コードスタイルチェック
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Rubyコード(RuboCop)
|
|
197
|
+
bundle exec rubocop
|
|
198
|
+
|
|
199
|
+
# Rustコード
|
|
200
|
+
cd ext/rbcsv
|
|
201
|
+
cargo fmt --check
|
|
202
|
+
cargo clippy -- -D warnings
|
|
203
|
+
cd ../..
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## API設計
|
|
207
|
+
|
|
208
|
+
### 現在のAPI(v0.1.6+)
|
|
209
|
+
|
|
210
|
+
```ruby
|
|
211
|
+
# 統一されたオプションベースAPI
|
|
212
|
+
RbCsv.parse(csv_string, options = {})
|
|
213
|
+
RbCsv.read(file_path, options = {})
|
|
214
|
+
|
|
215
|
+
# 利用可能なオプション
|
|
216
|
+
options = {
|
|
217
|
+
trim: true/false # 空白文字の除去(デフォルト: false)
|
|
218
|
+
# 将来の拡張:
|
|
219
|
+
# headers: true/false
|
|
220
|
+
# delimiter: ','
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### 実装アーキテクチャ
|
|
225
|
+
|
|
226
|
+
1. **parser.rs**: CsvParseOptionsと核となるCSV解析機能
|
|
227
|
+
2. **ruby_api.rs**: Rubyハッシュオプションの処理とMagnus API
|
|
228
|
+
3. **lib.rs**: Magnus初期化と関数登録
|
|
229
|
+
4. **error.rs**: エラーハンドリングとRuby例外の変換
|
|
230
|
+
|
|
231
|
+
## リリース手順
|
|
232
|
+
|
|
233
|
+
### 1. 準備フェーズ
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# 開発状況の確認
|
|
237
|
+
git status
|
|
238
|
+
git log --oneline -10
|
|
239
|
+
|
|
240
|
+
# 全テストの実行
|
|
241
|
+
rake clean
|
|
242
|
+
rake
|
|
243
|
+
|
|
244
|
+
# コードスタイルチェック
|
|
245
|
+
bundle exec rubocop
|
|
246
|
+
cd ext/rbcsv && cargo clippy && cd ../..
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### 2. バージョン更新
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# lib/rbcsv/version.rb を編集
|
|
253
|
+
vim lib/rbcsv/version.rb
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
module RbCsv
|
|
258
|
+
VERSION = "x.y.z" # セマンティックバージョニング
|
|
259
|
+
end
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 3. CHANGELOG.md の更新
|
|
263
|
+
|
|
264
|
+
```markdown
|
|
265
|
+
## [x.y.z] - YYYY-MM-DD
|
|
266
|
+
|
|
267
|
+
### 追加
|
|
268
|
+
- 新機能の説明
|
|
269
|
+
|
|
270
|
+
### 変更
|
|
271
|
+
- 既存機能の変更点
|
|
272
|
+
|
|
273
|
+
### 修正
|
|
274
|
+
- バグ修正の説明
|
|
275
|
+
|
|
276
|
+
### 削除
|
|
277
|
+
- 削除された機能(非互換性のある変更)
|
|
278
|
+
|
|
279
|
+
### セキュリティ
|
|
280
|
+
- セキュリティ関連の修正
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### 4. ビルドとテスト
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
# フルクリーンビルド
|
|
287
|
+
rake clean
|
|
288
|
+
bundle install
|
|
289
|
+
rake compile
|
|
290
|
+
|
|
291
|
+
# 統合テスト
|
|
292
|
+
rake spec
|
|
293
|
+
|
|
294
|
+
# 動作確認
|
|
295
|
+
ruby -I lib -e "require 'rbcsv'; puts RbCsv::VERSION"
|
|
296
|
+
ruby -I lib -e "require 'rbcsv'; p RbCsv.parse('a,b\n1,2', {})"
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 5. Gemビルド
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
# Gemファイル生成
|
|
303
|
+
gem build rbcsv.gemspec
|
|
304
|
+
|
|
305
|
+
# 生成確認
|
|
306
|
+
ls -la rbcsv-*.gem
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### 6. 変更のコミット
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
git add -A
|
|
313
|
+
git commit -m "Release v${VERSION}
|
|
314
|
+
|
|
315
|
+
主な変更:
|
|
316
|
+
- 変更点1の説明
|
|
317
|
+
- 変更点2の説明
|
|
318
|
+
- バグ修正やパフォーマンス改善"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 7. タグ作成とプッシュ
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
VERSION=$(ruby -I lib -e "require 'rbcsv/version'; puts RbCsv::VERSION")
|
|
325
|
+
git tag "v${VERSION}"
|
|
326
|
+
git push origin main
|
|
327
|
+
git push origin "v${VERSION}"
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 8. Gem公開(オプション)
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
# RubyGems.orgへの公開
|
|
334
|
+
gem push rbcsv-${VERSION}.gem
|
|
335
|
+
|
|
336
|
+
# 公開確認
|
|
337
|
+
gem list rbcsv --remote
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## 開発のベストプラクティス
|
|
341
|
+
|
|
342
|
+
### コードスタイル
|
|
343
|
+
|
|
344
|
+
#### Ruby
|
|
345
|
+
- 標準的なRuby Style Guideに従う
|
|
346
|
+
- RuboCop設定を使用(`.rubocop.yml`)
|
|
347
|
+
- frozen_string_literalを有効化
|
|
348
|
+
|
|
349
|
+
#### Rust
|
|
350
|
+
```bash
|
|
351
|
+
# フォーマット
|
|
352
|
+
cargo fmt
|
|
353
|
+
|
|
354
|
+
# リント
|
|
355
|
+
cargo clippy -- -D warnings
|
|
356
|
+
|
|
357
|
+
# ドキュメント生成
|
|
358
|
+
cargo doc --open
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### テスト戦略
|
|
362
|
+
|
|
363
|
+
1. **単体テスト**: 各Rustモジュールに対するcargo test
|
|
364
|
+
2. **統合テスト**: Ruby APIレベルでのRSpecテスト
|
|
365
|
+
3. **パフォーマンステスト**: 大きなCSVファイルでのベンチマーク
|
|
366
|
+
4. **エッジケーステスト**: 不正なCSV、空ファイル、エンコーディング問題
|
|
367
|
+
|
|
368
|
+
### デバッグ
|
|
369
|
+
|
|
370
|
+
#### Rust側のデバッグ
|
|
371
|
+
|
|
372
|
+
```rust
|
|
373
|
+
// 開発ビルドでのみ有効
|
|
374
|
+
#[cfg(debug_assertions)]
|
|
375
|
+
eprintln!("Debug: {:?}", variable);
|
|
376
|
+
|
|
377
|
+
// ログ出力(log crateを使用)
|
|
378
|
+
log::debug!("Debug information: {:?}", data);
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### Ruby側のデバッグ
|
|
382
|
+
|
|
383
|
+
```ruby
|
|
384
|
+
# 詳細エラー情報
|
|
385
|
+
begin
|
|
386
|
+
RbCsv.parse(invalid_csv, {})
|
|
387
|
+
rescue => e
|
|
388
|
+
puts "Error: #{e.class} - #{e.message}"
|
|
389
|
+
puts e.backtrace
|
|
390
|
+
end
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## よくある問題と解決策
|
|
394
|
+
|
|
395
|
+
### ビルド関連
|
|
396
|
+
|
|
397
|
+
**問題**: "incompatible ABI version"
|
|
398
|
+
```bash
|
|
399
|
+
# 解決策: クリーンして同じRubyバージョンで再ビルド
|
|
400
|
+
rm -rf lib/rbcsv/rbcsv.bundle tmp/
|
|
401
|
+
bundle exec rake compile
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**問題**: Rustコンパイルエラー
|
|
405
|
+
```bash
|
|
406
|
+
# 解決策: Rust依存関係の更新
|
|
407
|
+
cd ext/rbcsv
|
|
408
|
+
cargo update
|
|
409
|
+
cargo clean
|
|
410
|
+
cargo build --release
|
|
411
|
+
cd ../..
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### 実行時問題
|
|
415
|
+
|
|
416
|
+
**問題**: 空配列が返される
|
|
417
|
+
- **原因**: CSVリーダーの`has_headers`設定
|
|
418
|
+
- **解決策**: 最新バージョン(v0.1.4+)を使用
|
|
419
|
+
|
|
420
|
+
**問題**: 日本語CSV文字化け
|
|
421
|
+
- **原因**: エンコーディング問題
|
|
422
|
+
- **解決策**: UTF-8での保存を確認、またはエンコーディング変換
|
|
423
|
+
|
|
424
|
+
### パフォーマンス問題
|
|
425
|
+
|
|
426
|
+
**問題**: 大きなファイルでメモリ不足
|
|
427
|
+
- **解決策**: ストリーミング処理の実装を検討(将来の機能)
|
|
428
|
+
|
|
429
|
+
**問題**: 予想より遅い処理速度
|
|
430
|
+
- **チェック項目**:
|
|
431
|
+
- ファイルI/O vs メモリ処理
|
|
432
|
+
- trimオプションの使用
|
|
433
|
+
- デバッグビルド vs リリースビルド
|
|
434
|
+
|
|
435
|
+
## コントリビューションガイドライン
|
|
436
|
+
|
|
437
|
+
### 開発フロー
|
|
438
|
+
|
|
439
|
+
1. **Issue作成**: バグ報告や機能要求
|
|
440
|
+
2. **フォーク**: 個人リポジトリへのフォーク
|
|
441
|
+
3. **ブランチ作成**: `feature/new-feature` または `fix/bug-name`
|
|
442
|
+
4. **開発**: コードの実装とテスト追加
|
|
443
|
+
5. **テスト**: 全テストの実行と確認
|
|
444
|
+
6. **プルリクエスト**: 説明と変更内容の詳細
|
|
445
|
+
|
|
446
|
+
### コミットメッセージ
|
|
447
|
+
|
|
448
|
+
```
|
|
449
|
+
[種類] 簡潔な説明(50文字以内)
|
|
450
|
+
|
|
451
|
+
詳細な説明(必要に応じて):
|
|
452
|
+
- 変更の理由
|
|
453
|
+
- 実装方法
|
|
454
|
+
- 影響範囲
|
|
455
|
+
|
|
456
|
+
関連Issue: #123
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
種類の例:
|
|
460
|
+
- `feat`: 新機能
|
|
461
|
+
- `fix`: バグ修正
|
|
462
|
+
- `docs`: ドキュメント
|
|
463
|
+
- `style`: コードスタイル
|
|
464
|
+
- `refactor`: リファクタリング
|
|
465
|
+
- `test`: テスト追加
|
|
466
|
+
- `chore`: その他(依存関係更新など)
|
|
467
|
+
|
|
468
|
+
## ロードマップ
|
|
469
|
+
|
|
470
|
+
### 短期目標(v0.2.x)
|
|
471
|
+
- [ ] カスタム区切り文字サポート
|
|
472
|
+
- [ ] ヘッダー行処理
|
|
473
|
+
- [ ] エラーハンドリングの改善
|
|
474
|
+
|
|
475
|
+
### 中期目標(v0.3.x)
|
|
476
|
+
- [ ] ストリーミング処理
|
|
477
|
+
- [ ] 非同期処理サポート
|
|
478
|
+
- [ ] Windows完全サポート
|
|
479
|
+
|
|
480
|
+
### 長期目標(v1.0.x)
|
|
481
|
+
- [ ] 安定したAPI
|
|
482
|
+
- [ ] 包括的なドキュメント
|
|
483
|
+
- [ ] パフォーマンス最適化完了
|
|
484
|
+
|
|
485
|
+
## 参考リンク
|
|
486
|
+
|
|
487
|
+
- [Magnus Documentation](https://docs.rs/magnus/)
|
|
488
|
+
- [rb_sys Documentation](https://docs.rs/rb_sys/)
|
|
489
|
+
- [Ruby Extension Guide](https://docs.ruby-lang.org/en/master/extension_rdoc.html)
|
|
490
|
+
- [Cargo Book](https://doc.rust-lang.org/cargo/)
|
|
491
|
+
- [RubyGems Guides](https://guides.rubygems.org/)
|
data/ext/rbcsv/Cargo.toml
CHANGED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
use std::error::Error as StdError;
|
|
2
|
+
use std::fmt;
|
|
3
|
+
|
|
4
|
+
#[derive(Debug)]
|
|
5
|
+
pub struct CsvError {
|
|
6
|
+
message: String,
|
|
7
|
+
kind: ErrorKind,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
#[derive(Debug)]
|
|
11
|
+
pub enum ErrorKind {
|
|
12
|
+
// IO関連エラー
|
|
13
|
+
Io,
|
|
14
|
+
// CSV解析エラー
|
|
15
|
+
Parse,
|
|
16
|
+
// UTF-8エンコーディングエラー
|
|
17
|
+
Encoding,
|
|
18
|
+
// フィールド数の不一致
|
|
19
|
+
FieldCountMismatch,
|
|
20
|
+
// 空のCSVデータ
|
|
21
|
+
EmptyData,
|
|
22
|
+
// その他のエラー
|
|
23
|
+
#[allow(dead_code)]
|
|
24
|
+
Other,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
impl fmt::Display for CsvError {
|
|
28
|
+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
29
|
+
match self.kind {
|
|
30
|
+
ErrorKind::Io => write!(f, "IO Error: {}", self.message),
|
|
31
|
+
ErrorKind::Parse => write!(f, "Parse Error: {}", self.message),
|
|
32
|
+
ErrorKind::Encoding => write!(f, "Encoding Error: {}", self.message),
|
|
33
|
+
ErrorKind::FieldCountMismatch => write!(f, "Field Count Mismatch: {}", self.message),
|
|
34
|
+
ErrorKind::EmptyData => write!(f, "Empty Data: {}", self.message),
|
|
35
|
+
ErrorKind::Other => write!(f, "Error: {}", self.message),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl StdError for CsvError {}
|
|
41
|
+
|
|
42
|
+
impl CsvError {
|
|
43
|
+
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
|
|
44
|
+
CsvError {
|
|
45
|
+
message: message.into(),
|
|
46
|
+
kind,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub fn io(message: impl Into<String>) -> Self {
|
|
51
|
+
Self::new(ErrorKind::Io, message)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
pub fn parse(message: impl Into<String>) -> Self {
|
|
55
|
+
Self::new(ErrorKind::Parse, message)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pub fn encoding(message: impl Into<String>) -> Self {
|
|
59
|
+
Self::new(ErrorKind::Encoding, message)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#[allow(dead_code)]
|
|
63
|
+
pub fn field_count_mismatch(expected: usize, actual: usize) -> Self {
|
|
64
|
+
Self::new(
|
|
65
|
+
ErrorKind::FieldCountMismatch,
|
|
66
|
+
format!("Expected {} fields, but got {}", expected, actual),
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn empty_data() -> Self {
|
|
71
|
+
Self::new(ErrorKind::EmptyData, "CSV data is empty")
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// csv crateのエラーからの変換
|
|
76
|
+
impl From<csv::Error> for CsvError {
|
|
77
|
+
fn from(err: csv::Error) -> Self {
|
|
78
|
+
match err.kind() {
|
|
79
|
+
csv::ErrorKind::Io(_) => CsvError::io(err.to_string()),
|
|
80
|
+
csv::ErrorKind::Utf8 { .. } => CsvError::encoding(err.to_string()),
|
|
81
|
+
csv::ErrorKind::UnequalLengths { .. } => {
|
|
82
|
+
CsvError::new(ErrorKind::FieldCountMismatch, err.to_string())
|
|
83
|
+
}
|
|
84
|
+
_ => CsvError::parse(err.to_string()),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
data/ext/rbcsv/src/lib.rs
CHANGED
|
@@ -1,69 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
mod error;
|
|
2
|
+
mod parser;
|
|
3
|
+
mod ruby_api;
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
use magnus::{Object, Ruby};
|
|
6
|
+
use ruby_api::{parse, parse_trim, read, read_trim};
|
|
6
7
|
|
|
7
|
-
fn parse(s: String) -> Result<Vec<Vec<String>>, Error> {
|
|
8
|
-
let mut reader = csv::ReaderBuilder::new()
|
|
9
|
-
.has_headers(false) // ヘッダーを無効にして、すべての行を処理
|
|
10
|
-
.from_reader(s.as_bytes());
|
|
11
|
-
|
|
12
|
-
let mut records = Vec::new();
|
|
13
|
-
|
|
14
|
-
for result in reader.records() {
|
|
15
|
-
match result {
|
|
16
|
-
Ok(record) => {
|
|
17
|
-
let row: Vec<String> = record.iter().map(|field| field.to_string()).collect();
|
|
18
|
-
records.push(row);
|
|
19
|
-
}
|
|
20
|
-
#[cfg(not(test))]
|
|
21
|
-
Err(e) => return Err(Error::new(exception::runtime_error(), format!("CSV parse error: {}", e))),
|
|
22
|
-
#[cfg(test)]
|
|
23
|
-
Err(e) => return Err(Box::new(e)),
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
Ok(records)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
#[cfg(not(test))]
|
|
31
8
|
#[magnus::init]
|
|
32
|
-
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
9
|
+
fn init(ruby: &Ruby) -> Result<(), magnus::Error> {
|
|
33
10
|
let module = ruby.define_module("RbCsv")?;
|
|
34
|
-
module.define_singleton_method("parse", function!(parse, 1))?;
|
|
35
|
-
Ok(())
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
#[cfg(test)]
|
|
39
|
-
mod tests {
|
|
40
11
|
|
|
41
|
-
|
|
12
|
+
module.define_singleton_method("parse", magnus::function!(parse, 1))?;
|
|
13
|
+
module.define_singleton_method("parse!", magnus::function!(parse_trim, 1))?;
|
|
14
|
+
module.define_singleton_method("read", magnus::function!(read, 1))?;
|
|
15
|
+
module.define_singleton_method("read!", magnus::function!(read_trim, 1))?;
|
|
42
16
|
|
|
43
|
-
|
|
44
|
-
fn test_parse() {
|
|
45
|
-
let csv_data = "name,age,city\nAlice,25,Tokyo\nBob,30,Osaka";
|
|
46
|
-
let result = parse(csv_data.to_string());
|
|
47
|
-
|
|
48
|
-
assert!(result.is_ok());
|
|
49
|
-
|
|
50
|
-
let records = result.unwrap();
|
|
51
|
-
assert_eq!(records.len(), 3); // ヘッダー行も含むため3行
|
|
52
|
-
assert_eq!(records[0], vec!["name", "age", "city"]);
|
|
53
|
-
assert_eq!(records[1], vec!["Alice", "25", "Tokyo"]);
|
|
54
|
-
assert_eq!(records[2], vec!["Bob", "30", "Osaka"]);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
#[test]
|
|
58
|
-
fn test_parse_simple() {
|
|
59
|
-
let csv_data = "a,b\n1,2";
|
|
60
|
-
let result = parse(csv_data.to_string());
|
|
61
|
-
|
|
62
|
-
assert!(result.is_ok());
|
|
63
|
-
|
|
64
|
-
let records = result.unwrap();
|
|
65
|
-
assert_eq!(records.len(), 2);
|
|
66
|
-
assert_eq!(records[0], vec!["a", "b"]);
|
|
67
|
-
assert_eq!(records[1], vec!["1", "2"]);
|
|
68
|
-
}
|
|
17
|
+
Ok(())
|
|
69
18
|
}
|
|
19
|
+
|