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 @@
1
+ 2.3
@@ -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
@@ -0,0 +1,13 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+ This project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ### v0.1.1
6
+
7
+ ##### Fixed
8
+
9
+ * Travis config
10
+
11
+ ### v0.1.0
12
+
13
+ * Initial release.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in parxer.gemspec
4
+ gemspec
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
@@ -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.
@@ -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.
@@ -0,0 +1,5 @@
1
+ require 'rspec/core/rake_task'
2
+ require "bundler/gem_tasks"
3
+
4
+ task default: :spec
5
+ RSpec::Core::RakeTask.new
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,10 @@
1
+ require "parxer/version"
2
+ require "require_all"
3
+ require "active_support/all"
4
+ require "roo"
5
+ require "roo-xls"
6
+
7
+ require_rel "parxer"
8
+
9
+ module Parxer
10
+ end
@@ -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