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.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +14 -0
  4. data/.hound.yml +4 -0
  5. data/.rspec +2 -0
  6. data/.rubocop.yml +1038 -0
  7. data/.ruby-version +1 -0
  8. data/.travis.yml +12 -0
  9. data/CHANGELOG.md +13 -0
  10. data/Gemfile +4 -0
  11. data/Guardfile +5 -0
  12. data/LICENSE.txt +21 -0
  13. data/README.md +140 -0
  14. data/Rakefile +5 -0
  15. data/bin/console +14 -0
  16. data/bin/setup +8 -0
  17. data/docs/images/parxer-response.png +0 -0
  18. data/docs/images/superheroes-xls.png +0 -0
  19. data/lib/parxer.rb +10 -0
  20. data/lib/parxer/collections/attributes.rb +16 -0
  21. data/lib/parxer/collections/callbacks.rb +22 -0
  22. data/lib/parxer/collections/row_errors.rb +19 -0
  23. data/lib/parxer/collections/validators.rb +34 -0
  24. data/lib/parxer/dsl/dsl.rb +92 -0
  25. data/lib/parxer/formatters/base_formatter.rb +39 -0
  26. data/lib/parxer/formatters/boolean_formatter.rb +14 -0
  27. data/lib/parxer/formatters/custom_formatter.rb +13 -0
  28. data/lib/parxer/formatters/number_formatter.rb +25 -0
  29. data/lib/parxer/formatters/rut_formatter.rb +33 -0
  30. data/lib/parxer/formatters/string_formatter.rb +9 -0
  31. data/lib/parxer/parsers/base_parser.rb +80 -0
  32. data/lib/parxer/parsers/concerns/attributes.rb +25 -0
  33. data/lib/parxer/parsers/concerns/callback.rb +24 -0
  34. data/lib/parxer/parsers/concerns/config.rb +23 -0
  35. data/lib/parxer/parsers/concerns/formatter.rb +14 -0
  36. data/lib/parxer/parsers/concerns/validator.rb +75 -0
  37. data/lib/parxer/parsers/csv_parser.rb +13 -0
  38. data/lib/parxer/parsers/xls_parser.rb +21 -0
  39. data/lib/parxer/utils/context.rb +26 -0
  40. data/lib/parxer/utils/errors.rb +10 -0
  41. data/lib/parxer/utils/formatter_builder.rb +16 -0
  42. data/lib/parxer/utils/inherited_resource.rb +35 -0
  43. data/lib/parxer/utils/row_builder.rb +22 -0
  44. data/lib/parxer/validators/base_validator.rb +22 -0
  45. data/lib/parxer/validators/boolean_validator.rb +13 -0
  46. data/lib/parxer/validators/columns_validator.rb +19 -0
  47. data/lib/parxer/validators/custom_validator.rb +17 -0
  48. data/lib/parxer/validators/datetime_validator.rb +54 -0
  49. data/lib/parxer/validators/email_validator.rb +14 -0
  50. data/lib/parxer/validators/file_format_validator.rb +18 -0
  51. data/lib/parxer/validators/file_presence_validator.rb +9 -0
  52. data/lib/parxer/validators/inclusion_validator.rb +26 -0
  53. data/lib/parxer/validators/number_validator.rb +63 -0
  54. data/lib/parxer/validators/presence_validator.rb +9 -0
  55. data/lib/parxer/validators/rows_count_validator.rb +9 -0
  56. data/lib/parxer/validators/rut_validator.rb +32 -0
  57. data/lib/parxer/validators/url_validator.rb +11 -0
  58. data/lib/parxer/values/attribute.rb +19 -0
  59. data/lib/parxer/values/callback.rb +22 -0
  60. data/lib/parxer/values/row.rb +17 -0
  61. data/lib/parxer/version.rb +3 -0
  62. data/parxer.gemspec +32 -0
  63. 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,13 @@
1
+ module Parxer
2
+ module Validator
3
+ class Boolean < Base
4
+ OPTIONS = ["true", "t", "1", "false", "f", "0"]
5
+
6
+ def validate
7
+ v = context.value.to_s
8
+ return true if v.blank?
9
+ OPTIONS.include?(v)
10
+ end
11
+ end
12
+ end
13
+ 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,9 @@
1
+ module Parxer
2
+ module Validator
3
+ class FilePresence < Base
4
+ def validate
5
+ !context.file.to_s.blank?
6
+ end
7
+ end
8
+ end
9
+ 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,9 @@
1
+ module Parxer
2
+ module Validator
3
+ class Presence < Base
4
+ def validate
5
+ !context.value.to_s.blank?
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module Parxer
2
+ module Validator
3
+ class RowsCount < Base
4
+ def validate
5
+ context.rows_count <= config[:max].to_i
6
+ end
7
+ end
8
+ end
9
+ 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,11 @@
1
+ module Parxer
2
+ module Validator
3
+ class Url < Base
4
+ def validate
5
+ v = context.value.to_s
6
+ return true if v.blank?
7
+ !!(v =~ URI.regexp)
8
+ end
9
+ end
10
+ end
11
+ 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
@@ -0,0 +1,3 @@
1
+ module Parxer
2
+ VERSION = "0.1.1"
3
+ end
@@ -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