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
@@ -0,0 +1,22 @@
|
|
1
|
+
module Parxer
|
2
|
+
module RowBuilder
|
3
|
+
def self.build(attributes)
|
4
|
+
Class.new(Parxer::Row) do
|
5
|
+
def self.column_accessor(attribute)
|
6
|
+
attr_accessor(attribute) unless method_defined?(attribute)
|
7
|
+
rescue NameError
|
8
|
+
raise Parxer::RowError.new("invalid '#{attribute}' method name")
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_attribute(attribute, value = nil)
|
12
|
+
self.class.column_accessor(attribute)
|
13
|
+
send("#{attribute}=", value) if value
|
14
|
+
end
|
15
|
+
|
16
|
+
attributes.each do |attribute|
|
17
|
+
column_accessor(attribute)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Validator
|
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 id
|
14
|
+
self.class.name.demodulize.tableize.singularize.to_sym
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate
|
18
|
+
raise Parxer::ValidatorError.new("'validate' method not implemented")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Validator
|
3
|
+
class HeaderOrder < Base
|
4
|
+
def validate
|
5
|
+
context.attributes.each_with_index do |attribute, idx|
|
6
|
+
return false unless valid_header?(attribute, idx)
|
7
|
+
end
|
8
|
+
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def valid_header?(attribute, attribute_idx)
|
15
|
+
!!context.header[attribute_idx] && context.header[attribute_idx] == attribute.name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Validator
|
3
|
+
class Custom < Base
|
4
|
+
def id
|
5
|
+
config[:id].to_sym
|
6
|
+
end
|
7
|
+
|
8
|
+
def validate
|
9
|
+
if !config[:condition_proc].is_a?(Proc)
|
10
|
+
raise Parxer::ValidatorError.new("'condition_proc' needs to be a Proc")
|
11
|
+
end
|
12
|
+
|
13
|
+
instance_eval(&config[:condition_proc])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Validator
|
3
|
+
class Datetime < Base
|
4
|
+
def validate
|
5
|
+
v = context.value.to_s
|
6
|
+
return true if v.blank?
|
7
|
+
return false unless valid_datetime?(v)
|
8
|
+
return false unless valid_format?(v)
|
9
|
+
validate_range(v)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def valid_datetime?(v)
|
15
|
+
!!DateTime.parse(v)
|
16
|
+
rescue ArgumentError
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def date_format
|
21
|
+
config[:format]
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid_format?(v)
|
25
|
+
return true unless date_format
|
26
|
+
!!DateTime.strptime(v, date_format)
|
27
|
+
rescue ArgumentError
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate_range(v)
|
32
|
+
v = v.to_datetime
|
33
|
+
return false unless validate_limit(:gt, ">", v)
|
34
|
+
return false unless validate_limit(:gteq, ">=", v)
|
35
|
+
return false unless validate_limit(:lt, "<", v)
|
36
|
+
return false unless validate_limit(:lteq, "<=", v)
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_limit(limit_name, method, v)
|
41
|
+
r = valid_limit(limit_name)
|
42
|
+
return true if r.blank?
|
43
|
+
v.public_send(method, r)
|
44
|
+
end
|
45
|
+
|
46
|
+
def valid_limit(name)
|
47
|
+
limit = config[name].to_s
|
48
|
+
return if limit.blank?
|
49
|
+
return limit.to_datetime if valid_datetime?(limit)
|
50
|
+
raise Parxer::ValidatorError.new("'#{name}' has not a valid value for given datetime")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# rubocop:disable Metrics/LineLength
|
2
|
+
module Parxer
|
3
|
+
module Validator
|
4
|
+
class Email < Base
|
5
|
+
EMAIL_REGEXP = %r{\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z}
|
6
|
+
|
7
|
+
def validate
|
8
|
+
v = context.value.to_s
|
9
|
+
return true if v.blank?
|
10
|
+
!!(v =~ EMAIL_REGEXP)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Validator
|
3
|
+
class FileFormat < Base
|
4
|
+
def validate
|
5
|
+
return false unless allowed_extensions.include?(context.file_extension)
|
6
|
+
!!context.raw_rows
|
7
|
+
rescue
|
8
|
+
false
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def allowed_extensions
|
14
|
+
config[:allowed_extensions].map(&:to_sym)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Validator
|
3
|
+
class Inclusion < Base
|
4
|
+
def validate
|
5
|
+
v = context.value.to_s
|
6
|
+
return true if v.blank?
|
7
|
+
options.include?(v)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def options
|
13
|
+
@options ||= begin
|
14
|
+
opts = config[:in]
|
15
|
+
raise Parxer::ValidatorError.new("'in' config option is required") if opts.blank?
|
16
|
+
|
17
|
+
if !opts.is_a?(Array)
|
18
|
+
raise Parxer::ValidatorError.new("'in' config option needs to be Array")
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.map(&:to_s)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Validator
|
3
|
+
class Number < Base
|
4
|
+
def validate
|
5
|
+
v = context.value.to_s
|
6
|
+
return true if v.blank?
|
7
|
+
return false unless validate_number(v)
|
8
|
+
return false unless validate_negative(v)
|
9
|
+
validate_range(v)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def allow_negative?
|
15
|
+
!!config[:allow_negative]
|
16
|
+
end
|
17
|
+
|
18
|
+
def only_integer?
|
19
|
+
!!config[:only_integer]
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate_number(v)
|
23
|
+
only_integer? ? valid_integer?(v) : valid_float?(v)
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid_integer?(v)
|
27
|
+
!!(v.to_s =~ /\A[-+]?[0-9]+\z/)
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid_float?(v)
|
31
|
+
!!Float(v)
|
32
|
+
rescue ArgumentError
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_negative(v)
|
37
|
+
!(!allow_negative? && v.to_i.negative?)
|
38
|
+
end
|
39
|
+
|
40
|
+
def validate_range(v)
|
41
|
+
v = only_integer? ? v.to_i : v.to_f
|
42
|
+
return false unless validate_limit(:gt, ">", v)
|
43
|
+
return false unless validate_limit(:gteq, ">=", v)
|
44
|
+
return false unless validate_limit(:lt, "<", v)
|
45
|
+
return false unless validate_limit(:lteq, "<=", v)
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_limit(limit_name, method, v)
|
50
|
+
r = valid_limit(limit_name)
|
51
|
+
return true if r.blank?
|
52
|
+
v.public_send(method, r)
|
53
|
+
end
|
54
|
+
|
55
|
+
def valid_limit(name)
|
56
|
+
limit = config[name]
|
57
|
+
return if limit.blank?
|
58
|
+
return (only_integer? ? limit.to_i : limit.to_f) if validate_number(limit)
|
59
|
+
raise Parxer::ValidatorError.new("'#{name}' has not a valid value for given number type")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Parxer
|
2
|
+
module Validator
|
3
|
+
class Rut < Base
|
4
|
+
def validate
|
5
|
+
rut = clean_rut
|
6
|
+
return true if rut.blank?
|
7
|
+
t = rut[0...-1].to_i
|
8
|
+
m = 0
|
9
|
+
s = 1
|
10
|
+
while t.positive?
|
11
|
+
s = (s + t % 10 * (9 - m % 6)) % 11
|
12
|
+
m += 1
|
13
|
+
t /= 10
|
14
|
+
end
|
15
|
+
v = if s.positive? then (s - 1).to_s else 'K' end
|
16
|
+
(v == rut.last.upcase)
|
17
|
+
rescue
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def clean_rut
|
24
|
+
rut = context.value.to_s
|
25
|
+
rut.to_s.upcase!
|
26
|
+
rut.delete!(".")
|
27
|
+
rut.delete!("-")
|
28
|
+
rut
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Parxer
|
2
|
+
class Attribute
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_reader :id, :name, :validators, :formatter
|
6
|
+
|
7
|
+
def_delegators :validators, :add_validator
|
8
|
+
|
9
|
+
def initialize(id, name: nil)
|
10
|
+
@id = id.to_sym
|
11
|
+
@name = name
|
12
|
+
@validators = Parxer::Validators.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_formatter(formatter_name, config = {}, &block)
|
16
|
+
@formatter = Parxer::FormatterBuilder.build(formatter_name, config, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Parxer
|
2
|
+
class Callback
|
3
|
+
include Parxer::Context
|
4
|
+
|
5
|
+
attr_reader :type, :action, :config
|
6
|
+
|
7
|
+
def initialize(type: nil, action: nil, config: {})
|
8
|
+
@type = type.to_sym
|
9
|
+
@context = config.delete(:context)
|
10
|
+
@config = config
|
11
|
+
@action = action
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
if action.is_a?(Proc)
|
16
|
+
instance_eval(&action)
|
17
|
+
else
|
18
|
+
context.send(action)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Parxer
|
2
|
+
class Row
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_reader :idx
|
6
|
+
|
7
|
+
def_delegators :errors, :add_error, :attribute_error, :attribute_error?, :errors?
|
8
|
+
|
9
|
+
def initialize(idx: nil)
|
10
|
+
@idx = idx
|
11
|
+
end
|
12
|
+
|
13
|
+
def errors
|
14
|
+
@errors ||= Parxer::RowErrors.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/parxer.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "parxer/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "parxer"
|
9
|
+
spec.version = Parxer::VERSION
|
10
|
+
spec.authors = ["Platanus", "Leandro Segovia"]
|
11
|
+
spec.email = ["rubygems@platan.us", "ldlsegovia@gmail.com"]
|
12
|
+
spec.homepage = "https://github.com/platanus/parxer/master"
|
13
|
+
spec.summary = "ruby gem to parse data"
|
14
|
+
spec.description = "ruby gem to parse data from different source types"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "require_all"
|
23
|
+
spec.add_dependency "activesupport"
|
24
|
+
spec.add_dependency "roo"
|
25
|
+
spec.add_dependency "roo-xls"
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.4"
|
29
|
+
spec.add_development_dependency "guard-rspec", "~> 4.7"
|
30
|
+
spec.add_development_dependency "pry"
|
31
|
+
spec.add_development_dependency "coveralls"
|
32
|
+
end
|