parxer 0.1.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.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +14 -0
- data/.hound.yml +4 -0
- data/.rspec +2 -0
- data/.rubocop.yml +1038 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/CHANGELOG.md +13 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/Rakefile +5 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/images/parxer-response.png +0 -0
- data/docs/images/superheroes-xls.png +0 -0
- data/lib/parxer.rb +10 -0
- data/lib/parxer/collections/attributes.rb +16 -0
- data/lib/parxer/collections/callbacks.rb +22 -0
- data/lib/parxer/collections/row_errors.rb +19 -0
- data/lib/parxer/collections/validators.rb +34 -0
- data/lib/parxer/dsl/dsl.rb +92 -0
- data/lib/parxer/formatters/base_formatter.rb +39 -0
- data/lib/parxer/formatters/boolean_formatter.rb +14 -0
- data/lib/parxer/formatters/custom_formatter.rb +13 -0
- data/lib/parxer/formatters/number_formatter.rb +25 -0
- data/lib/parxer/formatters/rut_formatter.rb +33 -0
- data/lib/parxer/formatters/string_formatter.rb +9 -0
- data/lib/parxer/parsers/base_parser.rb +80 -0
- data/lib/parxer/parsers/concerns/attributes.rb +25 -0
- data/lib/parxer/parsers/concerns/callback.rb +24 -0
- data/lib/parxer/parsers/concerns/config.rb +23 -0
- data/lib/parxer/parsers/concerns/formatter.rb +14 -0
- data/lib/parxer/parsers/concerns/validator.rb +75 -0
- data/lib/parxer/parsers/csv_parser.rb +13 -0
- data/lib/parxer/parsers/xls_parser.rb +21 -0
- data/lib/parxer/utils/context.rb +26 -0
- data/lib/parxer/utils/errors.rb +10 -0
- data/lib/parxer/utils/formatter_builder.rb +16 -0
- data/lib/parxer/utils/inherited_resource.rb +35 -0
- data/lib/parxer/utils/row_builder.rb +22 -0
- data/lib/parxer/validators/base_validator.rb +22 -0
- data/lib/parxer/validators/boolean_validator.rb +13 -0
- data/lib/parxer/validators/columns_validator.rb +19 -0
- data/lib/parxer/validators/custom_validator.rb +17 -0
- data/lib/parxer/validators/datetime_validator.rb +54 -0
- data/lib/parxer/validators/email_validator.rb +14 -0
- data/lib/parxer/validators/file_format_validator.rb +18 -0
- data/lib/parxer/validators/file_presence_validator.rb +9 -0
- data/lib/parxer/validators/inclusion_validator.rb +26 -0
- data/lib/parxer/validators/number_validator.rb +63 -0
- data/lib/parxer/validators/presence_validator.rb +9 -0
- data/lib/parxer/validators/rows_count_validator.rb +9 -0
- data/lib/parxer/validators/rut_validator.rb +32 -0
- data/lib/parxer/validators/url_validator.rb +11 -0
- data/lib/parxer/values/attribute.rb +19 -0
- data/lib/parxer/values/callback.rb +22 -0
- data/lib/parxer/values/row.rb +17 -0
- data/lib/parxer/version.rb +3 -0
- data/parxer.gemspec +32 -0
- metadata +247 -0
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.3
|
data/.travis.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.3.1
|
4
|
+
before_install: gem install bundler -v 1.12.5
|
5
|
+
deploy:
|
6
|
+
provider: rubygems
|
7
|
+
api_key:
|
8
|
+
secure: wKW5LVpI/tvuXAJjWc3P6g9K1qlIphBm5xNaSZVVm8C3o7MMypsPLX7+U3Bhz23mgkyBI5JSUx46a7fNm2xeKdXKZ5g5i72zpxJ0g7NbD3kTg5iZdYIJw75qQKoMMdYEAwZa6ojVtFQV8r9kcC/nKrULruy7t2wC9eIJ4WeLo7IsU5McdAcT8c177p8YdS/Yh+V0wNu62rto8FByz9yvgyzXYAmKQ1Dni9xMttvC2bTAtnBO9/ghUQl0qNbqpJD7QZAockomB8uPqhcIjAdg5KNVsa7HZCvPXKn453oWVXFsXoZqFTMftU/cknbf+Gip0+KVMUHcW1Dqq5PAvEiTp46KtJEFWLKwMPl0QSgN3/5DM3zbLQIXFGcJkbrMno1BUlHdUVB6TZ9B7Owx37khOw1TEdvf1ROuV6qCn266hs7xxKdLMpD3keD6ONYzyxFBOXGsuSAin5BvRc9LwR44PRdFgHH7GvpgxX1ajujmNH2cfdabmsLsYEqee4ZIA2K7ZybLv1SvMEvfUau1O3Ahg/1xgHHPB0iYfu0CdqAUquzXcpyX1hce1ANqljCVBG8YhOfunNxTJ43vnE6L02PxOqQMah3rE47oeFyiGHokKNJg9NJwpTlXgucdOUogtoSXoYBGUA81TdqaJBcD2QOVAZlguuNLiXcPVwFyGTjsL54=
|
9
|
+
gem: parxer
|
10
|
+
on:
|
11
|
+
tags: true
|
12
|
+
repo: platanus/parxer
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright 2017 Platanus
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# Parxer
|
2
|
+
|
3
|
+
ruby gem to parse data from different source types
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```bash
|
8
|
+
$ gem install parxer
|
9
|
+
```
|
10
|
+
|
11
|
+
Or add to your Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem "parxer"
|
15
|
+
```
|
16
|
+
|
17
|
+
```bash
|
18
|
+
bundle install
|
19
|
+
```
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Imagine you have an `xls` file like this:
|
24
|
+
|
25
|
+
<img src="./docs/images/superheroes-xls.png" />
|
26
|
+
|
27
|
+
To parse this file you need to:
|
28
|
+
|
29
|
+
### 1 - Define your Parser using the DSL:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
class SuperheroesParser < Parxer::XlsParser
|
33
|
+
validate_file(:rows_count, max: 50) # Define file validators
|
34
|
+
|
35
|
+
column(:name, name: "Name") do # Map column names to attributes
|
36
|
+
validate(:presence) # Add column validator
|
37
|
+
format_with do
|
38
|
+
value.upcase # Define custom formatters
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
column(:real_name, name: "Real Name")
|
43
|
+
|
44
|
+
column(:super_power, name: "Super Power") do
|
45
|
+
validate(:presence)
|
46
|
+
end
|
47
|
+
|
48
|
+
column(:publisher, name: "Publisher") do
|
49
|
+
validate(:inclusion, in: ["Marvel", "DC"])
|
50
|
+
end
|
51
|
+
|
52
|
+
column(:age, name: "Alive") do
|
53
|
+
validate(:presence)
|
54
|
+
validate(:number)
|
55
|
+
format_with(:number, integer: true) # Use Parxer's formatter
|
56
|
+
end
|
57
|
+
|
58
|
+
column(:is_alive, name: "Alive", format: :boolean) do
|
59
|
+
validate(:presence)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Define validators to run after attribute validators
|
63
|
+
validate_row(:odd_chars, if_valid: [:name, :real_name]) do
|
64
|
+
(row.name + row.real_name).delete(" ").size.odd?
|
65
|
+
end
|
66
|
+
|
67
|
+
# Define callbacks
|
68
|
+
after_parse_row do
|
69
|
+
row.add_attribute(:full_name) # Add attributes dynamically
|
70
|
+
row.full_name = "#{row.name} (#{row.real_name})" unless row.errors?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
### 2 - Initialize your parser:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
parser = SuperheroesParser.new
|
79
|
+
```
|
80
|
+
|
81
|
+
### 3 - Use your parser:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
result = parser.run("/some_path/superhero.xls"); #=> #<Enumerator: ...>
|
85
|
+
```
|
86
|
+
|
87
|
+
Now, if you iterate through each row of the enumerator you will get something like this:
|
88
|
+
|
89
|
+
<img src="./docs/images/parxer-response.png" />
|
90
|
+
|
91
|
+
As you can see...
|
92
|
+
|
93
|
+
1. Attributes like `name` or `is_alive` have been formatted.
|
94
|
+
2. Errors in the rows are accessible through the `errors` attribute.
|
95
|
+
3. `idx` attribute is the row number.
|
96
|
+
4. the custom `full_name` attribute was added as a part of the response.
|
97
|
+
|
98
|
+
Some useful methods you can use are:
|
99
|
+
|
100
|
+
**In parser context:**
|
101
|
+
|
102
|
+
- `parser.valid_file?`
|
103
|
+
- `parser.file_error`
|
104
|
+
- `parser.run`
|
105
|
+
|
106
|
+
**In row (`row = result.next`) context:**
|
107
|
+
|
108
|
+
- `row.errors?`
|
109
|
+
- `row.errors`
|
110
|
+
- `row.attribute_error?(:some_attribute_name)`
|
111
|
+
|
112
|
+
## Testing
|
113
|
+
|
114
|
+
To run the specs you need to execute, **in the root path of the gem**, the following command:
|
115
|
+
|
116
|
+
```bash
|
117
|
+
bundle exec guard
|
118
|
+
```
|
119
|
+
|
120
|
+
You need to put **all your tests** in the `/my_gem/spec/` directory.
|
121
|
+
|
122
|
+
## Contributing
|
123
|
+
|
124
|
+
1. Fork it
|
125
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
126
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
127
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
128
|
+
5. Create new Pull Request
|
129
|
+
|
130
|
+
## Credits
|
131
|
+
|
132
|
+
Thank you [contributors](https://github.com/platanus/parxer/graphs/contributors)!
|
133
|
+
|
134
|
+
<img src="http://platan.us/gravatar_with_text.png" alt="Platanus" width="250"/>
|
135
|
+
|
136
|
+
Parxer is maintained by [platanus](http://platan.us).
|
137
|
+
|
138
|
+
## License
|
139
|
+
|
140
|
+
Parxer is © 2017 platanus, spa. It is free software and may be redistributed under the terms specified in the LICENSE file.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "parxer"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
Binary file
|
Binary file
|
data/lib/parxer.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module Parxer
|
2
|
+
class Attributes < Array
|
3
|
+
def add_attribute(id, name: nil)
|
4
|
+
if find_attribute(id)
|
5
|
+
raise Parxer::AttributesError.new("trying to add attribute with existent id")
|
6
|
+
end
|
7
|
+
|
8
|
+
self << Parxer::Attribute.new(id, name: name)
|
9
|
+
last
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_attribute(attribute_id)
|
13
|
+
find { |attribute| attribute.id == attribute_id.to_sym }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Parxer
|
2
|
+
class Callbacks < Array
|
3
|
+
CALLBACK_TYPES = %i{after_parse_row}
|
4
|
+
|
5
|
+
def add_callback(type, action, config = {})
|
6
|
+
if !CALLBACK_TYPES.include?(type.to_sym)
|
7
|
+
raise Parxer::CallbacksError.new("invalid '#{type}' callback type")
|
8
|
+
end
|
9
|
+
|
10
|
+
if !action.is_a?(Proc) && !action.is_a?(Symbol)
|
11
|
+
raise Parxer::CallbacksError.new("action param must by a Proc or symbol method name")
|
12
|
+
end
|
13
|
+
|
14
|
+
self << Parxer::Callback.new(type: type, action: action, config: config || {})
|
15
|
+
last
|
16
|
+
end
|
17
|
+
|
18
|
+
def by_type(type)
|
19
|
+
select { |callback| callback.type == type.to_sym }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Parxer
|
2
|
+
class RowErrors < Hash
|
3
|
+
def add_error(attribute_name, error)
|
4
|
+
self[attribute_name.to_sym] = error
|
5
|
+
end
|
6
|
+
|
7
|
+
def attribute_error?(attribute_name)
|
8
|
+
!!attribute_error(attribute_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def attribute_error(attribute_name)
|
12
|
+
self[attribute_name.to_sym]
|
13
|
+
end
|
14
|
+
|
15
|
+
def errors?
|
16
|
+
any?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Parxer
|
2
|
+
class Validators < Array
|
3
|
+
def add_validator(validator_name, config = {}, &block)
|
4
|
+
validator = validator_instance(validator_name, config, &block)
|
5
|
+
|
6
|
+
if find_validator(validator.id)
|
7
|
+
raise Parxer::ValidatorError.new("trying to add validator with existent id")
|
8
|
+
end
|
9
|
+
|
10
|
+
self << validator
|
11
|
+
last
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def find_validator(validator_id)
|
17
|
+
find { |validator| validator.id.to_sym == validator_id.to_sym }
|
18
|
+
end
|
19
|
+
|
20
|
+
def validator_instance(validator_name, config = {}, &block)
|
21
|
+
config[:id] = validator_name
|
22
|
+
validator_class = infer_validator_class(validator_name)
|
23
|
+
config[:condition_proc] = block if validator_class == Parxer::Validator::Custom
|
24
|
+
validator_class.new(config)
|
25
|
+
end
|
26
|
+
|
27
|
+
def infer_validator_class(validator_name)
|
28
|
+
return Parxer::Validator::Custom if validator_name.blank?
|
29
|
+
"Parxer::Validator::#{validator_name.to_s.camelize}".constantize
|
30
|
+
rescue NameError
|
31
|
+
Parxer::Validator::Custom
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Dsl
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class_methods do
|
6
|
+
def in_context
|
7
|
+
current_method = caller_locations(1, 1)[0].label.to_sym
|
8
|
+
validate_context!(current_method)
|
9
|
+
current_context << current_method
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
current_context.pop
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate_context!(current_method)
|
16
|
+
dependencies = ctx_dependencies_map[current_method]
|
17
|
+
|
18
|
+
if current_context != dependencies
|
19
|
+
msg = if dependencies.any?
|
20
|
+
"'#{current_method}' needs to run inside '#{dependencies.last}' block"
|
21
|
+
else
|
22
|
+
"'#{current_method}' can't run inside '#{current_context.last}' block"
|
23
|
+
end
|
24
|
+
|
25
|
+
raise Parxer::DslError.new(msg)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_aliases
|
30
|
+
@method_aliases ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def ctx_dependencies_map
|
34
|
+
{
|
35
|
+
add_parser_option: [],
|
36
|
+
validate_file: [],
|
37
|
+
validate_row: [],
|
38
|
+
column: [],
|
39
|
+
after_parse_row: [],
|
40
|
+
validate: [:column],
|
41
|
+
format_with: [:column]
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_context
|
46
|
+
@current_context ||= []
|
47
|
+
end
|
48
|
+
|
49
|
+
def column(id, config, &block)
|
50
|
+
in_context do
|
51
|
+
formatter_name = config.delete(:format)
|
52
|
+
@current_attr = attributes.add_attribute(id, name: config.delete(:name))
|
53
|
+
format_with(formatter_name) if formatter_name
|
54
|
+
block&.call
|
55
|
+
end
|
56
|
+
ensure
|
57
|
+
@current_attr = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate(validator_name, config = {}, &block)
|
61
|
+
in_context { @current_attr.add_validator(validator_name, config, &block) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def validate_file(validator_name, config = {}, &block)
|
65
|
+
in_context { add_validator(validator_name, config, &block) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_row(validator_name, config = {}, &block)
|
69
|
+
in_context { add_row_validator(validator_name, config, &block) }
|
70
|
+
end
|
71
|
+
|
72
|
+
def format_with(*params, &block)
|
73
|
+
in_context do
|
74
|
+
formatter_name = !params.first.is_a?(Hash) ? params.first : :custom
|
75
|
+
config = (params.first.is_a?(Hash) ? params.first : params.second) || {}
|
76
|
+
@current_attr.add_formatter(formatter_name, config, &block)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def after_parse_row(callback_method = nil, &block)
|
81
|
+
in_context do
|
82
|
+
action = callback_method || block
|
83
|
+
parser_callbacks.add_callback(:after_parse_row, action)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def add_parser_option(key, value)
|
88
|
+
in_context { add_config_option(key, value) }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Formatter
|
3
|
+
class Base
|
4
|
+
include Parxer::Context
|
5
|
+
|
6
|
+
attr_reader :config
|
7
|
+
|
8
|
+
def initialize(config = {})
|
9
|
+
@context = config.delete(:context)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply
|
14
|
+
v = context.value.to_s
|
15
|
+
|
16
|
+
if v.blank?
|
17
|
+
return default_value if default_value?
|
18
|
+
return nil
|
19
|
+
end
|
20
|
+
|
21
|
+
format_value(v)
|
22
|
+
end
|
23
|
+
|
24
|
+
def format_value(_v)
|
25
|
+
raise Parxer::FormatterError.new("'format_value' method not implemented")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def default_value?
|
31
|
+
!!default_value
|
32
|
+
end
|
33
|
+
|
34
|
+
def default_value
|
35
|
+
config[:default]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|