transproc 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/.codeclimate.yml +15 -0
  3. data/.gitignore +3 -0
  4. data/.travis.yml +14 -10
  5. data/CHANGELOG.md +22 -1
  6. data/Gemfile +5 -18
  7. data/README.md +3 -5
  8. data/Rakefile +1 -0
  9. data/lib/transproc.rb +3 -4
  10. data/lib/transproc/all.rb +2 -0
  11. data/lib/transproc/array.rb +5 -3
  12. data/lib/transproc/array/combine.rb +2 -0
  13. data/lib/transproc/class.rb +2 -0
  14. data/lib/transproc/coercions.rb +2 -0
  15. data/lib/transproc/compiler.rb +45 -0
  16. data/lib/transproc/composer.rb +2 -0
  17. data/lib/transproc/composite.rb +2 -0
  18. data/lib/transproc/conditional.rb +2 -0
  19. data/lib/transproc/constants.rb +5 -0
  20. data/lib/transproc/error.rb +2 -0
  21. data/lib/transproc/function.rb +2 -0
  22. data/lib/transproc/functions.rb +2 -0
  23. data/lib/transproc/hash.rb +83 -34
  24. data/lib/transproc/proc.rb +2 -0
  25. data/lib/transproc/recursion.rb +2 -0
  26. data/lib/transproc/registry.rb +1 -0
  27. data/lib/transproc/store.rb +1 -0
  28. data/lib/transproc/support/deprecations.rb +2 -0
  29. data/lib/transproc/transformer.rb +7 -1
  30. data/lib/transproc/transformer/class_interface.rb +31 -65
  31. data/lib/transproc/transformer/deprecated/class_interface.rb +80 -0
  32. data/lib/transproc/transformer/dsl.rb +51 -0
  33. data/lib/transproc/version.rb +3 -1
  34. data/spec/spec_helper.rb +15 -11
  35. data/spec/unit/array/combine_spec.rb +3 -1
  36. data/spec/unit/array_transformations_spec.rb +1 -1
  37. data/spec/unit/class_transformations_spec.rb +9 -6
  38. data/spec/unit/coercions_spec.rb +1 -1
  39. data/spec/unit/composer_spec.rb +1 -1
  40. data/spec/unit/conditional_spec.rb +1 -1
  41. data/spec/unit/function_not_found_error_spec.rb +1 -1
  42. data/spec/unit/function_spec.rb +1 -1
  43. data/spec/unit/hash_transformations_spec.rb +12 -1
  44. data/spec/unit/proc_transformations_spec.rb +3 -1
  45. data/spec/unit/recursion_spec.rb +1 -1
  46. data/spec/unit/registry_spec.rb +1 -1
  47. data/spec/unit/store_spec.rb +2 -1
  48. data/spec/unit/transformer/class_interface_spec.rb +364 -0
  49. data/spec/unit/transformer/dsl_spec.rb +15 -0
  50. data/spec/unit/transformer/instance_methods_spec.rb +25 -0
  51. data/spec/unit/transformer_spec.rb +128 -40
  52. data/spec/unit/transproc_spec.rb +1 -1
  53. data/transproc.gemspec +1 -5
  54. metadata +16 -54
  55. data/.rubocop.yml +0 -66
  56. data/.rubocop_todo.yml +0 -11
  57. data/rakelib/mutant.rake +0 -16
  58. data/rakelib/rubocop.rake +0 -18
  59. data/spec/support/mutant.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 64dbe08d203b9ac87e38de4e8f46dde2899d5cfc
4
- data.tar.gz: 6c4e2cd21783a8fea8e3f4ebfe05656ca8e1cd73
2
+ SHA256:
3
+ metadata.gz: bd2c6ee5f665083297c1fcfd89a5e37d2284e498d6bc3953b88314e9513cbf43
4
+ data.tar.gz: 6b9a5f270234b26b479095c66187f91fcca9428afb605f75aa9f51b890976eef
5
5
  SHA512:
6
- metadata.gz: e46205bc38e87eb30fa40d1466bfb0f1cff727a945d985f22bb17ae9ac2c3be4910363403c08f2092514f15c2de6d06b8d5863ec9db530fa600465ea9782c601
7
- data.tar.gz: 964e3d01aa66e74542a79ea9844977c299cab95984959d066336b3203b67364e4728938e5412a9cde5acbc7661d5f5d744e3d42d17dc89574856f4d56e8d1aff
6
+ metadata.gz: 1c79034b27d317e52be52e98eb0cce057400efc99c05203e16fd39112a911d0bb568511d58dd138bbc32568cf8951fab586dcdf90e3c9bff434b6fd93347044b
7
+ data.tar.gz: a2086dc6a92e03837412d7665869c1ad516a3a5ce097db1b31224936d767af048eadb32c86e8e0bcf815ef38445f9aed9a878b5f5ca97b5559b3194efa567aec
@@ -0,0 +1,15 @@
1
+ version: "2"
2
+
3
+ prepare:
4
+ fetch:
5
+ - url: "https://raw.githubusercontent.com/dry-rb/devtools/master/.rubocop.yml"
6
+ path: ".rubocop.yml"
7
+
8
+ exclude_patterns:
9
+ - "benchmarks/"
10
+ - "examples/"
11
+ - "spec/"
12
+
13
+ plugins:
14
+ rubocop:
15
+ enabled: true
data/.gitignore CHANGED
@@ -13,3 +13,6 @@
13
13
  *.o
14
14
  *.a
15
15
  mkmf.log
16
+ .rubocop.yml
17
+ .rubocop_todo.yml
18
+
@@ -1,18 +1,22 @@
1
- dist: trusty
2
1
  language: ruby
3
- sudo: required
4
2
  cache: bundler
5
3
  bundler_args: --without tools
4
+ before_script:
5
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
6
+ - chmod +x ./cc-test-reporter
7
+ - ./cc-test-reporter before-build
8
+ after_script:
9
+ - "[ -d coverage ] && ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT"
6
10
  script: 'bundle exec rake spec'
7
- after_success:
8
- - '[ "$TRAVIS_RUBY_VERSION" = "2.3.1" ] && [ "$TRAVIS_BRANCH" = "master" ] && bundle exec codeclimate-test-reporter'
9
11
  rvm:
10
- - 2.0
11
- - 2.2
12
- - 2.1
13
- - 2.3.1
14
- - rbx-3
15
- - jruby-9.1.5.0
12
+ - 2.3.8
13
+ - 2.6.3
14
+ - 2.5.5
15
+ - 2.4.6
16
+ - jruby-9.2.7.0
17
+ env:
18
+ global:
19
+ - COVERAGE='true'
16
20
  notifications:
17
21
  webhooks:
18
22
  urls:
@@ -1,3 +1,24 @@
1
+ # v1.1.0 2019-07-18
2
+
3
+ This is the last transproc release before the project will be forked to `dry-transformer`.
4
+
5
+ ## Added
6
+
7
+ * New DSL for defining transformers using `define!` method, which now supports instance methods as transformation functions (@solnic)
8
+ * Simplified transformer class definition - registry is auto-configured and you can use `import` at the transformer class level (@solnic)
9
+ * New `HashTransformation.deep_stringify_keys` function
10
+
11
+ [Compare v1.0.3...v1.1.0](https://github.com/solnic/transproc/compare/v1.0.3...v1.1.0)
12
+
13
+ # v1.0.3 2018-12-01
14
+
15
+ ## Changed
16
+
17
+ * [BREAKING] Added minimal Ruby version to the gemspec file. Transproc now works with Ruby 2.3 and above (flash-gordon)
18
+ * Performance improvements introduced by using new built-in methods in `Hash` (v-kolesnikov + flash-gordon)
19
+
20
+ [Compare v1.0.2...v1.0.3](https://github.com/solnic/transproc/compare/v1.0.2...v1.0.3)
21
+
1
22
  # v1.0.2 2017-02-25
2
23
 
3
24
  ## Fixed
@@ -8,7 +29,7 @@
8
29
 
9
30
  # v1.0.1 2017-02-25
10
31
 
11
- * `combine` is now multiple times faster, depending on the level of nesting (Kukunin + splattael)
32
+ * `combine` is now multiple times faster, depending on the level of nesting (Kukunin + splattael)
12
33
  * `nest` (thus `wrap` too) is ~2x faster now (solnic)
13
34
 
14
35
  [Compare v1.0.0...v1.0.1](https://github.com/solnic/transproc/compare/v1.0.0...v1.0.1)
data/Gemfile CHANGED
@@ -2,28 +2,15 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- group :test do
6
- gem 'equalizer'
5
+ gem 'rake'
6
+ gem 'rspec', '~> 3.8'
7
+ gem 'dry-equalizer', '~> 0.2'
7
8
 
8
- if RUBY_VERSION >= '2.1'
9
- gem 'anima'
10
- platform :mri do
11
- gem 'mutant', github: 'mbj/mutant', branch: 'master'
12
- gem 'mutant-rspec'
13
-
14
- gem 'codeclimate-test-reporter', require: false
15
- gem 'simplecov', require: false
16
- end
17
- else
18
- gem 'anima', '~> 0.2.0'
19
- end
9
+ platform :mri do
10
+ gem 'simplecov', require: false
20
11
  end
21
12
 
22
13
  group :tools do
23
- gem 'rubocop', '~> 0.30.0'
24
14
  gem 'byebug', platform: :mri
25
15
  gem 'benchmark-ips'
26
- gem 'guard'
27
- gem 'guard-rspec'
28
- gem 'guard-rubocop'
29
16
  end
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  [gem]: https://rubygems.org/gems/transproc
2
2
  [travis]: https://travis-ci.org/solnic/transproc
3
- [gemnasium]: https://gemnasium.com/solnic/transproc
4
3
  [codeclimate]: https://codeclimate.com/github/solnic/transproc
5
4
  [coveralls]: https://coveralls.io/r/solnic/transproc
6
5
  [inchpages]: http://inch-ci.org/github/solnic/transproc
@@ -9,7 +8,6 @@
9
8
 
10
9
  [![Gem Version](https://badge.fury.io/rb/transproc.svg)][gem]
11
10
  [![Build Status](https://travis-ci.org/solnic/transproc.svg?branch=master)][travis]
12
- [![Dependency Status](https://gemnasium.com/solnic/transproc.svg)][gemnasium]
13
11
  [![Code Climate](https://codeclimate.com/github/solnic/transproc/badges/gpa.svg)][codeclimate]
14
12
  [![Test Coverage](https://codeclimate.com/github/solnic/transproc/badges/coverage.svg)][codeclimate]
15
13
  [![Inline docs](http://inch-ci.org/github/solnic/transproc.svg?branch=master)][inchpages]
@@ -197,9 +195,9 @@ transformation = t(:camel_case)
197
195
  transformation.call 'i_am_a_camel'
198
196
  # => "IAmACamel"
199
197
 
200
- transformation = t(:map_array, t(:symbolize_keys)
201
- .>> t(:rename_keys, user_name: :user))
202
- .>> t(:wrap, :address, [:city, :street, :zipcode])
198
+ transformation = t(:map_array, (
199
+ t(:symbolize_keys).>> t(:rename_keys, user_name: :user)
200
+ )).>> t(:wrap, :address, [:city, :street, :zipcode])
203
201
 
204
202
  transformation.call(
205
203
  [
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require 'bundler/gem_tasks'
1
2
  require 'rspec/core/rake_task'
2
3
 
3
4
  task default: :spec
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'transproc/version'
4
+ require 'transproc/constants'
2
5
  require 'transproc/function'
3
6
  require 'transproc/functions'
4
7
  require 'transproc/composer'
@@ -8,9 +11,5 @@ require 'transproc/registry'
8
11
  require 'transproc/transformer'
9
12
  require 'transproc/support/deprecations'
10
13
 
11
- module Transproc
12
- Undefined = Object.new.freeze
13
- end
14
-
15
14
  require 'transproc/array'
16
15
  require 'transproc/hash'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'transproc'
2
4
 
3
5
  require 'transproc/class'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'transproc/coercions'
2
4
  require 'transproc/hash'
3
5
  require 'transproc/array/combine'
@@ -65,7 +67,7 @@ module Transproc
65
67
  # Group array values using provided root key and value keys
66
68
  #
67
69
  # @example
68
- # fn = Transproc(:group, :tags, [:tag_name])
70
+ # fn = Transproc(:group, :tags, [:tag])
69
71
  #
70
72
  # fn.call [
71
73
  # { task: 'Group it', tag: 'task' },
@@ -98,10 +100,10 @@ module Transproc
98
100
  # Ungroup array values using provided root key and value keys
99
101
  #
100
102
  # @example
101
- # fn = Transproc(:group, :tags, [:tag_name])
103
+ # fn = Transproc(:ungroup, :tags, [:tag])
102
104
  #
103
105
  # fn.call [
104
- # { task: 'Group it', tags: [{ tag: 'task' }, { tag: 'important' }]
106
+ # { task: 'Group it', tags: [{ tag: 'task' }, { tag: 'important' }] }
105
107
  # ]
106
108
  # # => [
107
109
  # { task: 'Group it', tag: 'task' },
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Transproc
2
4
  module ArrayTransformations
3
5
  class Combine
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Transproc
2
4
  # Transformation functions for Classes
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'date'
2
4
  require 'time'
3
5
  require 'bigdecimal'
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Transproc
4
+ # @api private
5
+ class Compiler
6
+ InvalidFunctionNameError = Class.new(StandardError)
7
+
8
+ attr_reader :registry, :transformer
9
+
10
+ def initialize(registry, transformer = nil)
11
+ @registry = registry
12
+ @transformer = transformer
13
+ end
14
+
15
+ def call(ast)
16
+ ast.map(&method(:visit)).reduce(:>>)
17
+ end
18
+
19
+ def visit(node)
20
+ id, *rest = node
21
+ public_send(:"visit_#{id}", *rest)
22
+ end
23
+
24
+ def visit_fn(node)
25
+ name, rest = node
26
+ args = rest.map { |arg| visit(arg) }
27
+
28
+ if registry.contain?(name)
29
+ registry[name, *args]
30
+ elsif transformer.respond_to?(name)
31
+ Function.new(transformer.method(name), name: name, args: args)
32
+ else
33
+ raise InvalidFunctionNameError, "function name +#{name}+ is not valid"
34
+ end
35
+ end
36
+
37
+ def visit_arg(arg)
38
+ arg
39
+ end
40
+
41
+ def visit_t(node)
42
+ call(node)
43
+ end
44
+ end
45
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'transproc/support/deprecations'
2
4
 
3
5
  module Transproc
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Transproc
2
4
  # Composition of two functions
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Transproc
2
4
  # Conditional transformation functions
3
5
  #
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Transproc
4
+ Undefined = Object.new.freeze
5
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Transproc
2
4
  Error = Class.new(StandardError)
3
5
  FunctionAlreadyRegisteredError = Class.new(Error)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'transproc/composite'
2
4
 
3
5
  module Transproc
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Transproc
2
4
  # Function container extension
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'transproc/coercions'
2
4
 
3
5
  module Transproc
@@ -17,20 +19,26 @@ module Transproc
17
19
  module HashTransformations
18
20
  extend Registry
19
21
 
20
- # Map all keys in a hash with the provided transformation function
21
- #
22
- # @example
23
- # Transproc(:map_keys, -> s { s.upcase })['name' => 'Jane']
24
- # # => {"NAME" => "Jane"}
25
- #
26
- # @param [Hash]
27
- #
28
- # @return [Hash]
29
- #
30
- # @api public
31
- def self.map_keys(source_hash, fn)
32
- Hash[source_hash].tap do |hash|
33
- hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
22
+ if RUBY_VERSION >= '2.5'
23
+ # Map all keys in a hash with the provided transformation function
24
+ #
25
+ # @example
26
+ # Transproc(:map_keys, -> s { s.upcase })['name' => 'Jane']
27
+ # # => {"NAME" => "Jane"}
28
+ #
29
+ # @param [Hash]
30
+ #
31
+ # @return [Hash]
32
+ #
33
+ # @api public
34
+ def self.map_keys(source_hash, fn)
35
+ Hash[source_hash].transform_keys!(&fn)
36
+ end
37
+ else
38
+ def self.map_keys(source_hash, fn)
39
+ Hash[source_hash].tap do |hash|
40
+ hash.keys.each { |key| hash[fn[key]] = hash.delete(key) }
41
+ end
34
42
  end
35
43
  end
36
44
 
@@ -94,20 +102,55 @@ module Transproc
94
102
  map_keys(hash, Coercions[:to_string].fn)
95
103
  end
96
104
 
97
- # Map all values in a hash using transformation function
105
+ # Stringify keys in a hash recursively
98
106
  #
99
107
  # @example
100
- # Transproc(:map_values, -> v { v.upcase })[:name => 'Jane']
101
- # # => {"name" => "JANE"}
108
+ # input = { :foo => "bar", :baz => [{ :one => 1 }] }
109
+ #
110
+ # t(:deep_stringify_keys)[input]
111
+ # # => { "foo" => "bar", "baz" => [{ "one" => 1 }] }
102
112
  #
103
113
  # @param [Hash]
104
114
  #
105
115
  # @return [Hash]
106
116
  #
107
117
  # @api public
108
- def self.map_values(source_hash, fn)
109
- Hash[source_hash].tap do |hash|
110
- hash.each { |key, value| hash[key] = fn[value] }
118
+ def self.deep_stringify_keys(hash)
119
+ hash.each_with_object({}) do |(key, value), output|
120
+ output[key.to_s] =
121
+ case value
122
+ when Hash
123
+ deep_stringify_keys(value)
124
+ when Array
125
+ value.map { |item|
126
+ item.is_a?(Hash) ? deep_stringify_keys(item) : item
127
+ }
128
+ else
129
+ value
130
+ end
131
+ end
132
+ end
133
+
134
+ if RUBY_VERSION >= '2.4'
135
+ # Map all values in a hash using transformation function
136
+ #
137
+ # @example
138
+ # Transproc(:map_values, -> v { v.upcase })[:name => 'Jane']
139
+ # # => {"name" => "JANE"}
140
+ #
141
+ # @param [Hash]
142
+ #
143
+ # @return [Hash]
144
+ #
145
+ # @api public
146
+ def self.map_values(source_hash, fn)
147
+ Hash[source_hash].transform_values!(&fn)
148
+ end
149
+ else
150
+ def self.map_values(source_hash, fn)
151
+ Hash[source_hash].tap do |hash|
152
+ hash.each { |key, value| hash[key] = fn[value] }
153
+ end
111
154
  end
112
155
  end
113
156
 
@@ -167,20 +210,26 @@ module Transproc
167
210
  Hash[hash].reject { |k, _| keys.include?(k) }
168
211
  end
169
212
 
170
- # Accepts specified keys from a hash
171
- #
172
- # @example
173
- # Transproc(:accept_keys, [:name])[name: 'Jane', email: 'jane@doe.org']
174
- # # => {:name=>"Jane"}
175
- #
176
- # @param [Hash] hash The input hash
177
- # @param [Array] keys The keys to be accepted
178
- #
179
- # @return [Hash]
180
- #
181
- # @api public
182
- def self.accept_keys(hash, keys)
183
- reject_keys(hash, hash.keys - keys)
213
+ if RUBY_VERSION >= '2.5'
214
+ # Accepts specified keys from a hash
215
+ #
216
+ # @example
217
+ # Transproc(:accept_keys, [:name])[name: 'Jane', email: 'jane@doe.org']
218
+ # # => {:name=>"Jane"}
219
+ #
220
+ # @param [Hash] hash The input hash
221
+ # @param [Array] keys The keys to be accepted
222
+ #
223
+ # @return [Hash]
224
+ #
225
+ # @api public
226
+ def self.accept_keys(hash, keys)
227
+ Hash[hash].slice(*keys)
228
+ end
229
+ else
230
+ def self.accept_keys(hash, keys)
231
+ reject_keys(hash, hash.keys - keys)
232
+ end
184
233
  end
185
234
 
186
235
  # Map a key in a hash with the provided transformation function