parxer 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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