README.md in csv2hash-0.0.2 vs README.md in csv2hash-0.1
- old
+ new
@@ -7,11 +7,11 @@
[![Build Status](https://travis-ci.org/joel/csv2hash.png?branch=master)](https://travis-ci.org/joel/csv2hash) (Travis CI)
[![Coverage Status](https://coveralls.io/repos/joel/csv2hash/badge.png)](https://coveralls.io/r/joel/csv2hash)
-It's DSL for valided and map CSV to Ruby Hash
+It is a DSL to validate and map a CSV to a Ruby Hash.
## Installation
Add this line to your application's Gemfile:
@@ -25,176 +25,264 @@
$ gem install csv2hash
## Usage
-#### Rules
+Parsing is based on rules, you must defined rules of parsing
-You should be declare an definition for you CSV, for each cells you should define what you expect.
+### Rules
+You should declare a definition for you CSV, and then define for each cell what you would expect.
+
Example :
-You want first cell parsed should be string with values are 'yes' or 'no' you must fill follow rule :
+You want the cell located on first line, first column to be a string with its values to be 'yes' or 'no'. Then you can right the following validation rule :
{ name: 'aswering', type: 'string', values: ['yes', 'no'], position: [0,0] }
-All keys as default values, so you can just define this rule :
+The type is 'string' by default, so can just write:
{ name: 'aswering', values: ['yes', 'no'], position: [0,0] }
-You can define message, default is 'undefined :key on :position'
+You can define a message, default is 'undefined :key on :position'
{ name: 'aswering', values: ['yes', 'no'], position: [0,0], message: 'this value is not supported' }
You can also define Range
{ name: 'score', values: 0..5, position: [0,0] }
-if you insert key on you message they will be substituted
+The message is parsed:
{ ..., message: 'value of :name is not supported, please you one of :values' }
-produce :
+It produces :
-
value of aswering is not supported, please you one of [yes, no]
-##### Position
+### Default values
-Position mean [Y, X], where Y is rows, X columns
+Only position is required:
-#### Definition
+* :position
-You should provide a definition, you have 2 types of definitions, mapping definition for search on x,y in your data or collection definition for rules apply for all lines in x, so you position rules should be only x value
+All remaining keys are optionals:
-### Sample
+* message: 'undefined :key on :position'
+* mappable: true
+* type: 'string'
+* values: nil
+* nested: nil
+* allow_blank: false
+* extra_validator: nil
-#### Mapping
+## Define where your data is expected
-Consider csv data like that
+**IMPORTANT!** Position mean [Y, X], where Y is rows, X columns
+A definition should be provided. There are 2 types of definitions:
+* search for data at a precise position in the table: `y,x`
+* or search for data in a column of rows, where all the rows are the same: `x` (column index)
+
+## Samples
+
+### Validation of a cell at a precise position
+
+Consider the following CSV:
+
| Fields | Person Informations | Optional |
|-------------|----------------------|----------|
-| Nickname | john | no |
+| Nickname | jo | no |
| First Name | John | yes |
| Last Name | Doe | yes |
-Mapping sample definition
+Precise position validation sample:
class MyParser
attr_accessor :file_path
def initialize file_path
@file_path = file_path
end
+ def data
+ @data_wrapper ||= Csv2hash.new(definition, file_path).parse
+ end
+
+ private
+
def rules
[].tap do |mapping|
mapping << { position: [2,1], key: 'first_name' }
mapping << { position: [3,1], key: 'last_name' }
end
end
def definition
- Definition.new(rules, type = Definition::MAPPING)
+ Csv2Hash::Definition.new(rules, type = Csv2Hash::Definition::MAPPING, 1)
end
- def data
- Csv2hash.new(definition, file_path).tap do |csv2hash|
- csv2hash.parse
- end.data
- end
-
end
-#### Collection
+### Validation of a collection
-Consider csv data like that
+Consider the following CSV:
| Nickname | First Name | Last Name |
|----------|------------|-----------|
-| john | John | Doe |
-| jane | Jane | Doe |
+| jo | John | Doe |
+| ja | Jane | Doe |
-Mapping sample definition
+Collection validation sample:
class MyParser
attr_accessor :file_path
def initialize file_path
@file_path = file_path
end
+ def data
+ @data_wrapper ||= Csv2hash.new(definition, file_path).parse
+ end
+
+ private
+
def rules
[].tap do |mapping|
mapping << { position: 0, key: 'nickname' }
mapping << { position: 1, key: 'first_name' }
mapping << { position: 2, key: 'last_name' }
end
end
def definition
- Definition.new(rules, type = Definition::COLLECTION)
+ Csv2Hash::Definition.new(rules, type = Csv2Hash::Definition::MAPPING, 1)
end
- def data
- Csv2hash.new(definition, file_path).tap do |csv2hash|
- csv2hash.parse
- end.data
- end
-
end
-#### Headers
+### CSV Headers
-You should be define header size
+You can define the number of rows to skip in the header of the CSV.
Definition.new(rules, type, header_size=0)
-#### Exception or CSV mode
+### Parser and configuration
-You can choice 2 mode of parsing, either exception mode for raise exception in first breaking rules or csv mode for get csv original data + errors throwing into added columns.
+Pasrer take severale parameters like that :
+ initialize definition, file_path, exception_mode=true, data_source=nil, ignore_blank_line=false
-parse return data or csv_with_errors if parse is invalid, you can plug this like that :
+you can pass directly Array of data (Array at 2 dimensions) really useful for testing, if you don't care about line blank in your CSV you can ignore them.
- csv2hash = Csv2hash.new(definition, 'file_path').new
- result = csv2hash.parse
- return result if csv2hash.valid?
+### Response
- filename = 'issues_errors.csv'
- tempfile = Tempfile.new [filename, File.extname(filename)]
- File.open(tempfile.path, 'wb') { |file| file.write result }
+The parser return values wrapper into DataWrapper Object, you can call .valid? method on this Object and grab either data or errors like that :
- # Send mail with csv file + errors and free resource
+ response = parser.parse
+ if response.valid?
+ response.data
+ else
+ response.errors
+ end
- tempfile.unlink
+data or errors are Array, but errors can be formatted on csv format with .to_csv call
+ response.errors.to_csv
-#### Default values
+## Exception or Not !
-only position is require
+You can choice 2 modes of parsing, either **exception mode** for raise exception in first breaking rules or **csv mode** for get csv original data + errors throwing into added columns.
-* :position
+### On **CSV MODE** you can choose different way for manage errors
-all remaind keys are optionals
+`.parse()` return `data_wrapper` if `.parse()` is invalid, you can code your own behavior like that :
-* message: 'undefined :key on :position'
-* mappable: true
-* type: 'string'
-* values: nil
-* nested: nil
-* allow_blank: false
+in your code
+ parser = Csv2hash.new(definition, 'file_path').new
+ response = parser.parse
+ return response if response.valid?
+ # Whatever
+
+In the same time Csv2hash call **notify(response)** method when CSV parsing fail, you can add your own Notifier like that
+
+ module Csv2hash
+ module Plugins
+ class Notifier
+ def initialize csv2hash
+ csv2hash.notifier.extend NotifierWithEmail
+ end
+
+ module NotifierWithEmail
+ def notify response
+ filename = 'issues_errors.csv'
+ tempfile = Tempfile.new [filename, File.extname(filename)]
+ File.open(tempfile.path, 'wb') { |file| file.write response.errors.to_csv }
+ # Send mail with csv file + errors and free resource
+ tempfile.unlink
+ end
+ end
+ end
+ end
+ end
+
+Or other implementation
+
+### Errors Format
+
+errors is a Array of Hash
+
+ { y: 1, x: 0, message: 'message', key: 'key', value: '' }
+
+## Sample
+
+### Csv data
+
+| Fields | Person Informations |
+|-------------|----------------------|
+| Nickname | nil |
+
+### Rule
+
+ { position: [1,1], key: 'nickname', allow_blank: false }
+
+### Error
+
+ { y: 1, x: 1, message: 'undefined nikcname on [0, 0]', key: 'nickname', value: nil }
+## Personal Validator Rule
+
+You can define your own Validator
+
+For downcase validation
+
+ class DowncaseValidator < Csv2hash::ExtraValidator
+ def valid? value
+ !!(value.match /^[a-z]+$/)
+ end
+ end
+
+in your rule
+
+ { position: [0,0], key: 'name', extra_validator: DowncaseValidator.new,
+ message: 'your data should be writting in downcase only' }
+
+Csv data
+
+ [ [ 'Foo' ] ]
+
+
### Limitations
+* Don't manage number of column you expected on collection mode
## Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Added some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
-5. Create new Pull Request
+5. Create new Pull Request
\ No newline at end of file