transproc 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +15 -0
  3. data/.gitignore +3 -0
  4. data/.travis.yml +10 -10
  5. data/CHANGELOG.md +14 -2
  6. data/Gemfile +5 -17
  7. data/lib/transproc.rb +3 -4
  8. data/lib/transproc/all.rb +2 -0
  9. data/lib/transproc/array.rb +2 -0
  10. data/lib/transproc/array/combine.rb +2 -0
  11. data/lib/transproc/class.rb +2 -0
  12. data/lib/transproc/coercions.rb +2 -0
  13. data/lib/transproc/compiler.rb +45 -0
  14. data/lib/transproc/composer.rb +2 -0
  15. data/lib/transproc/composite.rb +2 -0
  16. data/lib/transproc/conditional.rb +2 -0
  17. data/lib/transproc/constants.rb +5 -0
  18. data/lib/transproc/error.rb +2 -0
  19. data/lib/transproc/function.rb +2 -0
  20. data/lib/transproc/functions.rb +2 -0
  21. data/lib/transproc/hash.rb +31 -0
  22. data/lib/transproc/proc.rb +2 -0
  23. data/lib/transproc/recursion.rb +2 -0
  24. data/lib/transproc/registry.rb +1 -0
  25. data/lib/transproc/store.rb +1 -0
  26. data/lib/transproc/support/deprecations.rb +2 -0
  27. data/lib/transproc/transformer.rb +7 -1
  28. data/lib/transproc/transformer/class_interface.rb +31 -65
  29. data/lib/transproc/transformer/deprecated/class_interface.rb +80 -0
  30. data/lib/transproc/transformer/dsl.rb +51 -0
  31. data/lib/transproc/version.rb +3 -1
  32. data/spec/spec_helper.rb +5 -7
  33. data/spec/unit/array/combine_spec.rb +3 -1
  34. data/spec/unit/array_transformations_spec.rb +1 -1
  35. data/spec/unit/class_transformations_spec.rb +9 -6
  36. data/spec/unit/coercions_spec.rb +1 -1
  37. data/spec/unit/composer_spec.rb +1 -1
  38. data/spec/unit/conditional_spec.rb +1 -1
  39. data/spec/unit/function_not_found_error_spec.rb +1 -1
  40. data/spec/unit/function_spec.rb +1 -1
  41. data/spec/unit/hash_transformations_spec.rb +12 -1
  42. data/spec/unit/proc_transformations_spec.rb +3 -1
  43. data/spec/unit/recursion_spec.rb +1 -1
  44. data/spec/unit/registry_spec.rb +1 -1
  45. data/spec/unit/store_spec.rb +2 -1
  46. data/spec/unit/transformer/class_interface_spec.rb +364 -0
  47. data/spec/unit/transformer/dsl_spec.rb +15 -0
  48. data/spec/unit/transformer/instance_methods_spec.rb +25 -0
  49. data/spec/unit/transformer_spec.rb +128 -40
  50. data/spec/unit/transproc_spec.rb +1 -1
  51. data/transproc.gemspec +0 -4
  52. metadata +15 -53
  53. data/.rubocop.yml +0 -66
  54. data/.rubocop_todo.yml +0 -11
  55. data/rakelib/mutant.rake +0 -16
  56. data/rakelib/rubocop.rake +0 -18
  57. data/spec/support/mutant.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d88691992987b997ac6deeec1affc3a04a1657fc4ed3521f84c6b26adcce5a1
4
- data.tar.gz: dc1f06315b9c3a039c6694a14e4cd379b6836b1710fafe0123a05e985441eca8
3
+ metadata.gz: bd2c6ee5f665083297c1fcfd89a5e37d2284e498d6bc3953b88314e9513cbf43
4
+ data.tar.gz: 6b9a5f270234b26b479095c66187f91fcca9428afb605f75aa9f51b890976eef
5
5
  SHA512:
6
- metadata.gz: a2288e68392270823d3dcc4543534553c0d1ac504d95f84f4f9ccc54f52f682dfb64118aecfa913226247c24cc1d77d367193df29ef89a14255f9870c6ae3425
7
- data.tar.gz: 65b3916bc564ad16fab85fd84bb66a75544ee7924b4df50673727f212cbc551dc2106a594a052b47093dff1653c7e92e1e464e74facc9cd1aa51ae7c8084e3d0
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,21 +1,21 @@
1
- dist: trusty
2
1
  language: ruby
3
- sudo: required
4
2
  cache: bundler
5
3
  bundler_args: --without tools
6
- before_script: gem update --system
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"
7
10
  script: 'bundle exec rake spec'
8
- after_success:
9
- - '[ -d coverage ] && bundle exec codeclimate-test-reporter'
10
11
  rvm:
11
12
  - 2.3.8
12
- - 2.4.5
13
- - 2.5.3
14
- - 2.6
15
- - jruby-9.2.4.1
13
+ - 2.6.3
14
+ - 2.5.5
15
+ - 2.4.6
16
+ - jruby-9.2.7.0
16
17
  env:
17
18
  global:
18
- - JRUBY_OPTS='--dev -J-Xmx1024M'
19
19
  - COVERAGE='true'
20
20
  notifications:
21
21
  webhooks:
@@ -1,11 +1,23 @@
1
- # v1.0.3 to-be-released
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
2
14
 
3
15
  ## Changed
4
16
 
5
17
  * [BREAKING] Added minimal Ruby version to the gemspec file. Transproc now works with Ruby 2.3 and above (flash-gordon)
6
18
  * Performance improvements introduced by using new built-in methods in `Hash` (v-kolesnikov + flash-gordon)
7
19
 
8
- [Compare v1.0.2...master](https://github.com/solnic/transproc/compare/v1.0.2...master)
20
+ [Compare v1.0.2...v1.0.3](https://github.com/solnic/transproc/compare/v1.0.2...v1.0.3)
9
21
 
10
22
  # v1.0.2 2017-02-25
11
23
 
data/Gemfile CHANGED
@@ -2,27 +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
- gem 'anima'
9
-
10
- platform :mri do
11
- if RUBY_VERSION >= '2.5'
12
- gem 'mutant', github: 'mbj/mutant', branch: 'master'
13
- gem 'mutant-rspec'
14
- end
15
-
16
- gem 'codeclimate-test-reporter', require: false
17
- gem 'simplecov', require: false
18
- end
9
+ platform :mri do
10
+ gem 'simplecov', require: false
19
11
  end
20
12
 
21
13
  group :tools do
22
- gem 'rubocop', '~> 0.30.0'
23
14
  gem 'byebug', platform: :mri
24
15
  gem 'benchmark-ips'
25
- gem 'guard'
26
- gem 'guard-rspec'
27
- gem 'guard-rubocop'
28
16
  end
@@ -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'
@@ -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
@@ -100,6 +102,35 @@ module Transproc
100
102
  map_keys(hash, Coercions[:to_string].fn)
101
103
  end
102
104
 
105
+ # Stringify keys in a hash recursively
106
+ #
107
+ # @example
108
+ # input = { :foo => "bar", :baz => [{ :one => 1 }] }
109
+ #
110
+ # t(:deep_stringify_keys)[input]
111
+ # # => { "foo" => "bar", "baz" => [{ "one" => 1 }] }
112
+ #
113
+ # @param [Hash]
114
+ #
115
+ # @return [Hash]
116
+ #
117
+ # @api public
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
+
103
134
  if RUBY_VERSION >= '2.4'
104
135
  # Map all values in a hash using transformation function
105
136
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Transproc
2
4
  # Transformation functions for Procs
3
5
  #
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'transproc/conditional'
2
4
 
3
5
  module Transproc
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Transproc
4
5
  # Container to define transproc functions in, and access them via `[]` method
@@ -1,4 +1,5 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module Transproc
4
5
  # Immutable collection of named procedures from external modules
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Transproc
2
4
  module Deprecations
3
5
  def self.announce(name, msg)
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'transproc/transformer/class_interface'
4
+ require 'transproc/transformer/deprecated/class_interface'
2
5
 
3
6
  module Transproc
4
7
  # Transfomer class for defining transprocs with a class DSL.
@@ -45,6 +48,9 @@ module Transproc
45
48
  # @api public
46
49
  class Transformer
47
50
  extend ClassInterface
51
+ extend Deprecated::ClassInterface
52
+
53
+ attr_reader :transproc
48
54
 
49
55
  # Execute the transformation pipeline with the given input.
50
56
  #
@@ -63,7 +69,7 @@ module Transproc
63
69
  #
64
70
  # @api public
65
71
  def call(input)
66
- self.class.transproc.call(input)
72
+ transproc.call(input)
67
73
  end
68
74
  end
69
75
  end
@@ -1,7 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'transproc/transformer/dsl'
4
+
1
5
  module Transproc
2
6
  class Transformer
3
- # @api private
7
+ # @api public
4
8
  module ClassInterface
9
+ # @api private
10
+ attr_reader :dsl
11
+
5
12
  # Return a base Transproc::Transformer class with the
6
13
  # container configured to the passed argument.
7
14
  #
@@ -17,14 +24,18 @@ module Transproc
17
24
  #
18
25
  # @api public
19
26
  def [](container)
20
- klass = Class.new(Transformer)
27
+ klass = Class.new(self)
21
28
  klass.container(container)
22
29
  klass
23
30
  end
24
31
 
25
32
  # @api private
26
33
  def inherited(subclass)
34
+ super
35
+
27
36
  subclass.container(@container) if defined?(@container)
37
+
38
+ subclass.instance_variable_set('@dsl', dsl.dup) if dsl
28
39
  end
29
40
 
30
41
  # Get or set the container to resolve transprocs from.
@@ -45,41 +56,32 @@ module Transproc
45
56
  # @return [Transproc::Registry]
46
57
  #
47
58
  # @api private
48
- def container(container = ::Transproc::Undefined)
49
- if container == ::Transproc::Undefined
50
- ensure_container_presence!
51
- @container
59
+ def container(container = Undefined)
60
+ if container.equal?(Undefined)
61
+ @container ||= Module.new.extend(Transproc::Registry)
52
62
  else
53
63
  @container = container
54
64
  end
55
65
  end
56
66
 
57
- # Define an anonymous transproc derived from given Transformer
58
- # Evaluates block with transformations and returns initialized transproc.
59
- # Does not mutate original Transformer
60
- #
61
- # @example
62
- #
63
- # class MyTransformer < Transproc::Transformer[MyContainer]
64
- # end
65
- #
66
- # transproc = MyTransformer.define do
67
- # map_values t(:to_string)
68
- # end
69
- # transproc.call(a: 1, b: 2)
70
- # # => {a: '1', b: '2'}
71
- #
72
- # @yield Block allowing to define transformations. The same as class level DSL
73
- #
74
- # @return [Function] Composed transproc
75
- #
76
67
  # @api public
77
- def define(&block)
78
- return transproc unless block_given?
68
+ def import(*args)
69
+ container.import(*args)
70
+ end
71
+
72
+ # @api public
73
+ def define!(&block)
74
+ @dsl ||= DSL.new(container)
75
+ @dsl.instance_eval(&block)
76
+ self
77
+ end
79
78
 
80
- Class.new(self).tap { |klass| klass.instance_eval(&block) }.transproc
79
+ # @api public
80
+ def new(*args)
81
+ super(*args).tap do |transformer|
82
+ transformer.instance_variable_set('@transproc', dsl.(transformer)) if dsl
83
+ end
81
84
  end
82
- alias build define
83
85
 
84
86
  # Get a transformation from the container,
85
87
  # without adding it to the transformation pipeline
@@ -105,42 +107,6 @@ module Transproc
105
107
  def t(fn, *args)
106
108
  container[fn, *args]
107
109
  end
108
-
109
- # @api private
110
- def method_missing(method, *args, &block)
111
- if container.contain?(method)
112
- args.push(define(&block)) if block_given?
113
- transformations << t(method, *args)
114
- else
115
- super
116
- end
117
- end
118
-
119
- # @api private
120
- def respond_to_missing?(method, _include_private = false)
121
- container.contain?(method) || super
122
- end
123
-
124
- # @api private
125
- def transproc
126
- transformations.reduce(:>>)
127
- end
128
-
129
- private
130
-
131
- # An array containing the transformation pipeline
132
- #
133
- # @api private
134
- def transformations
135
- @transformations ||= []
136
- end
137
-
138
- # @api private
139
- def ensure_container_presence!
140
- return if defined?(@container)
141
- raise ArgumentError, 'Transformer function registry is empty. '\
142
- 'Provide your registry via Transproc::Transformer[YourRegistry]'
143
- end
144
110
  end
145
111
  end
146
112
  end