hashformer 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cfc27e5ad9d2e3e9869c1f0095821e63553fefdf
4
- data.tar.gz: 51b608ef72f5494b086d0e1853f73b0044ed0462
3
+ metadata.gz: 05a8926c5ecb538a6ddf72a14f118d655a18852a
4
+ data.tar.gz: 967d6cd3decd71173baafa6dd21745a0ebfa1542
5
5
  SHA512:
6
- metadata.gz: f493351b32ba8a4c35db7d6b027300e475846181a6a6187a19a3b2c66205d773a4033729c4328a15b8d67965834268ea9abd6158008dae45eb23239c4b78e1df
7
- data.tar.gz: b580f15b4ef7056065904841b8ed57656b1447c9c0ee39d0334d1aa51079e212747e45f1049590ef9143e1eeacedb2b7d02c36579b1c026881be45ce29301df2
6
+ metadata.gz: e4f0f3d3b1161500ca05fafd4c3949ee3e5660be691a3051c0f3823b74d2c3d713c76e212db9116dc73ee68e192cb9caec61a1a52cfc50a1b7f92eb1f3a99844
7
+ data.tar.gz: 15d32b0a42af0a007028793131e7fc69493c5228eedc4b468ac5e0831a678859d9e0c780f44aa0bc56bb9d5b26715d906a4d360b29fa82241501f7f60b58e562
data/.rspec CHANGED
@@ -1 +1,2 @@
1
1
  --color
2
+ --require 'spec_helper'
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - '2.0'
5
+ - '2.1'
6
+ - '2.2'
7
+ - '2.3.1'
8
+ - jruby-19mode
9
+ script: bundle exec rspec
10
+ before_install:
11
+ - 'gem install bundler'
data/Gemfile CHANGED
@@ -1,19 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- group :test do
4
- # RSpec for tests
5
- gem 'rspec'
6
-
7
- # SimpleCov for test coverage
8
- gem 'simplecov', '~> 0.7.1', require: false
9
-
10
- # Code Climate test coverage
11
- gem "codeclimate-test-reporter", require: nil
12
- end
13
-
14
- group :development do
15
- gem 'debugger', platforms: [:ruby_19]
16
- gem 'byebug', platforms: [:ruby_20, :ruby_21]
17
- end
18
-
19
3
  gemspec
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  Hashformer
2
2
  =========
3
- [![Gem Version](https://badge.fury.io/rb/hashformer.svg)](http://badge.fury.io/rb/hashformer) [![Code Climate](https://codeclimate.com/github/deseretbook/hashformer.png)](https://codeclimate.com/github/deseretbook/hashformer) [![Test Coverage](https://codeclimate.com/github/deseretbook/hashformer/coverage.png)](https://codeclimate.com/github/deseretbook/hashformer) [![Codeship Status for deseretbook/hashformer](https://www.codeship.io/projects/dd988da0-dee7-0131-9e92-7e1ff0bec112/status?branch=master)](https://www.codeship.io/projects/24888)
3
+ [![Gem Version](https://badge.fury.io/rb/hashformer.svg)](http://badge.fury.io/rb/hashformer) [![Code Climate](https://codeclimate.com/github/deseretbook/hashformer.png)](https://codeclimate.com/github/deseretbook/hashformer) [![Test Coverage](https://codeclimate.com/github/deseretbook/hashformer/coverage.png)](https://codeclimate.com/github/deseretbook/hashformer) [![Build Status](https://travis-ci.org/deseretbook/hashformer.svg)](https://travis-ci.org/deseretbook/hashformer)
4
4
 
5
5
  ### Transform any Ruby Hash with a declarative DSL
6
6
 
@@ -14,9 +14,17 @@ keys, input keys, and transformations, and Hashformer will convert your data
14
14
  into the format you specify. It can also help verify your transformations by
15
15
  validating input and output data using [Classy Hash](https://github.com/deseretbook/classy_hash).
16
16
 
17
+ Note that Hashformer is not for everyone. If your data transformation needs
18
+ don't involve massive changes to the data structure or values, and/or you don't
19
+ need multiple people to be able to work on the transformations separately from
20
+ other code, you might be better off doing your transformations in plain Ruby.
21
+
17
22
 
18
23
  ### Examples
19
24
 
25
+ Examples of each feature are provided here, but complete documentation for each
26
+ method lives in the code.
27
+
20
28
  #### Basic renaming
21
29
 
22
30
  If you just need to move/copy/rename keys, you specify the source key as the
@@ -147,7 +155,8 @@ This is the most useful and powerful aspect of Hashformer. You can use
147
155
  lookups:
148
156
 
149
157
  _**Note:** Method chaining may not work as expected if entered in `irb`, because
150
- `irb` might try to call `#to_s` or `#inspect` on the method chain!_
158
+ `irb` might try to call `#to_s` or `#inspect` on the method chain! See `.__end`
159
+ and `.enable_debugging` for possible solutions_
151
160
 
152
161
  ```ruby
153
162
  data = {
@@ -210,6 +219,56 @@ Hashformer.transform({x: -12}, xform)
210
219
  # => {x: 29}
211
220
  ```
212
221
 
222
+ ##### `__as`
223
+
224
+ The special `__as` method on a method chain, added in version 0.3.0, allows you
225
+ to work with the chain's current value in a block like `Object#tap`, but the
226
+ return value of the block is passed to the next step of the chain. This is
227
+ useful if you need to pass the chain value to an outside function.
228
+
229
+ ```ruby
230
+ def func(x)
231
+ "something to do with #{x}"
232
+ end
233
+
234
+ xform = {
235
+ out: HF[:in].__as{|v| 'test ' + func(v) }
236
+ }
237
+
238
+ Hashformer.transform({ in: 'code' }, xform)
239
+ # => { out: 'something to do with test code' }
240
+ ```
241
+
242
+ ##### `__end`
243
+
244
+ The `__end` method on a method chain will disable further modification of the
245
+ chain. This is not normally needed unless your transformation Hashes might be
246
+ `#inspect`ed by other code (e.g. IRB or Pry). Using `__end` might prevent you
247
+ from needing to enable chain debugging.
248
+
249
+ ```ruby
250
+ xform = {
251
+ # Everything after __end will be ignored, including __as
252
+ out: HF[:in].to_s.__end.to_i.no.more.methods
253
+ }
254
+
255
+ Hashformer.transform({ in: 100 })
256
+ # => { out: '100' }
257
+ ```
258
+
259
+ ##### Debugging chains
260
+
261
+ If `__end` isn't enough to make your method chains work with whatever debugging
262
+ or instrumentation you have, you can enable chain debugging. *When chain
263
+ debugging is enabled, any standard `Object` methods cannot be added to chains
264
+ (this includes commonly chained methods like `#to_s`).* Each method added to a
265
+ chain will also be printed to `$stdout`.
266
+
267
+ ```ruby
268
+ HF::G::Chain.enable_debugging
269
+ HF::G::Chain.disable_debugging
270
+ ```
271
+
213
272
 
214
273
  #### Mapping one or more values
215
274
 
@@ -232,7 +291,8 @@ Hashformer.transform(data, xform)
232
291
  ```
233
292
 
234
293
  You can also mix and match paths and method chains in the `HF::G.map`
235
- parameters:
294
+ parameters. The result of the method chain transformation or path retrieval
295
+ will be used in the map, instead of looking up a key in the original hash:
236
296
 
237
297
  ```ruby
238
298
  data = {
@@ -347,6 +407,29 @@ Hashformer.transform(data, xform)
347
407
  # => {b: { n: 1, o: 2, p: 5 }}
348
408
  ```
349
409
 
410
+ #### Dates and times
411
+
412
+ We found ourselves writing a lot of identical date transformation `Proc`s in our
413
+ transformations, so version 0.3.0 adds some helpers for transforming dates to
414
+ and from numeric values. If you use Hashformer in a project that also uses
415
+ ActiveSupport, you can transform time zones as well.
416
+
417
+ ```ruby
418
+ xform = {
419
+ int: HF::Date.to_i(:time),
420
+ float: HF::Date.to_f(:time),
421
+ date: HF::Date.to_date(:numeric),
422
+ }
423
+
424
+ data = {
425
+ time: Time.at(10.75),
426
+ numeric: 10.75
427
+ }
428
+
429
+ Hashformer.transform(data, xform)
430
+ # => { int: 10, float: 10.75, date: #<DateTime 1970-01-01...}
431
+ ```
432
+
350
433
 
351
434
  #### Practical example with validation
352
435
 
data/hashformer.gemspec CHANGED
@@ -21,8 +21,14 @@ Gem::Specification.new do |spec|
21
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.required_ruby_version = '>= 1.9.3'
24
+ spec.required_ruby_version = '>= 2.0'
25
25
 
26
- spec.add_development_dependency "bundler", "~> 1.5"
27
26
  spec.add_runtime_dependency "classy_hash", "~> 0.1", ">= 0.1.1"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.11"
29
+ spec.add_development_dependency "rspec", "~> 3.5"
30
+ spec.add_development_dependency "simplecov", "~> 0.12.0"
31
+ spec.add_development_dependency "codeclimate-test-reporter"
32
+ spec.add_development_dependency "byebug", "~> 9.0" unless RUBY_PLATFORM == 'java'
33
+ spec.add_development_dependency "activesupport", "~> 4.2"
28
34
  end
@@ -0,0 +1,93 @@
1
+ # Hashformer date and time transformation generators.
2
+ # Created July 2016 by Mike Bourgeous, DeseretBook.com
3
+ # Copyright (C)2016 Deseret Book
4
+ # See LICENSE and README.md for details.
5
+
6
+ require 'date'
7
+ require 'time'
8
+
9
+ module Hashformer
10
+ module Date
11
+ # Generates a Proc to convert a Date, Time, or DateTime to an Integer UNIX
12
+ # timestamp. Passes nil through unmodified. Useful for transforming to
13
+ # serialization formats that don't support dates directly.
14
+ #
15
+ # Returns a method chain that can be further modified.
16
+ #
17
+ # Raises an error during transformation if the input value is not nil and
18
+ # is a different class.
19
+ #
20
+ # Example:
21
+ # data = { time: Time.at(0.75), no_time: nil }
22
+ # xform = { ts: HF::Date.to_i(:time), ts2: HF::Date.to_i(:no_time) }
23
+ # HF.transform(data, xform)
24
+ # # => { ts: 0, ts2: nil }
25
+ def self.to_i(key)
26
+ HF[key].__as{|d|
27
+ d = d.to_time if d.is_a?(::Date)
28
+ raise "Invalid date/time class #{d.class}" unless d.nil? || d.is_a?(Time) || d.is_a?(DateTime)
29
+
30
+ d && d.to_i
31
+ }
32
+ end
33
+
34
+ # Generates a Proc to convert a Date, Time, or DateTime to a floating point
35
+ # UNIX timestamp. Passes nil through unmodified. Useful for transforming
36
+ # to serialization formats that don't support dates directly.
37
+ #
38
+ # Returns a method chain that can be further modified.
39
+ #
40
+ # Raises an error during transformation if the input value is not nil and
41
+ # is a different class.
42
+ #
43
+ # Example:
44
+ # data = { time: Time.at(0.75), no_time: nil }
45
+ # xform = { ts: HF::Date.to_f(:time), ts2: HF::Date.to_f(:no_time) }
46
+ # HF.transform(data, xform)
47
+ # # => { ts: 0.75, ts2: nil }
48
+ def self.to_f(key)
49
+ HF[key].__as{|d|
50
+ d = d.to_time if d.is_a?(::Date)
51
+ raise "Invalid date/time class #{d.class}" unless d.nil? || d.is_a?(Time) || d.is_a?(DateTime)
52
+
53
+ d && d.to_f
54
+ }
55
+ end
56
+
57
+ # Generates a Proc to convert an Integer or Numeric UNIX timestamp to a
58
+ # DateTime. Passes nil through unmodified. Useful for transforming from
59
+ # serialization formats that don't support dates directly to database
60
+ # interfaces that might expect DateTime rather than Time.
61
+ #
62
+ # If +tz_name+ is the default special value of :utc, then the resulting
63
+ # DateTime will be in UTC time. If +tz_name+ is nil, the default zone will
64
+ # be used. Otherwise, if +tz_name+ is given and the Time class responds to
65
+ # #in_time_zone (requires ActiveSupport::TimeWithZone, which is not loaded
66
+ # by Hashformer), then the date will be converted to the given named
67
+ # timezone.
68
+ #
69
+ # Returns a method chain that can be further modified.
70
+ #
71
+ # Raises an error during transformation if the input value is not a type
72
+ # supported by Time.at().
73
+ #
74
+ # Example:
75
+ def self.to_date(key, tz_name = :utc)
76
+ if tz_name == :utc
77
+ HF[key].__as{|d|
78
+ d && Time.at(d).utc.to_datetime
79
+ }
80
+ elsif tz_name
81
+ raise 'ActiveSupport time helpers are required for tz_name' unless Time.instance_methods.include?(:in_time_zone)
82
+
83
+ HF[key].__as{|d|
84
+ d && Time.at(d).in_time_zone(tz_name).to_datetime
85
+ }
86
+ else
87
+ HF[key].__as{|d|
88
+ d && Time.at(d).to_datetime
89
+ }
90
+ end
91
+ end
92
+ end
93
+ end
@@ -62,22 +62,53 @@ module Hashformer
62
62
  # Internal representation of a method call and array lookup chainer. Do
63
63
  # not use this directly; instead use HF::G.chain().
64
64
  class Chain
65
- # Receiver for chaining calls that has no methods of its own except
66
- # initialize. This allows methods like :call to be chained.
67
- #
68
- # IMPORTANT: No methods other than .__chain can be called on this object,
69
- # because they will be chained! Instead, use === to detect the object's
70
- # type, for example.
71
- class Receiver < BasicObject
65
+ # Base module that defines methods included by BasicReceiver and
66
+ # DebuggableReceiver.
67
+ module ReceiverMethods
72
68
  # An oddly named accessor is used instead of #initialize to avoid
73
69
  # conflicts with any methods that might be chained.
74
70
  attr_accessor :__chain
75
71
 
76
- # Adds a method call or array dereference to the list of calls to apply.
72
+ # Adds a method call or array dereference to the list of calls to
73
+ # apply. Does nothing if #__end has been called. Returns self for
74
+ # more chaining.
77
75
  def method_missing(name, *args, &block)
78
- @__chain << {name: name, args: args, block: block}
76
+ @__ended ||= false
77
+ @__chain << {name: name, args: args, block: block} unless @__ended
78
+ self
79
+ end
80
+
81
+ # Adds a call to the given +block+ to the chain like Object#tap, but
82
+ # returns the result of the block instead of the original object. Any
83
+ # arguments given will be passed to the +block+ after the current
84
+ # value. Does nothing if #__end has been called. Returns self for
85
+ # more chaining.
86
+ #
87
+ # This is similar in spirit (but not implementation) to
88
+ # http://stackoverflow.com/a/12849214
89
+ def __as(*args, &block)
90
+ ::Kernel.raise 'No block given to #__as' unless ::Kernel.block_given?
91
+ @__ended ||= false
92
+ @__chain << {args: args, block: block} unless @__ended
93
+ self
94
+ end
95
+
96
+ # Disables further chaining. Any future method calls will just return
97
+ # the existing chain without modifying it.
98
+ def __end
99
+ @__ended = true
79
100
  self
80
101
  end
102
+ end
103
+
104
+ # Receiver for chaining calls that has no methods of its own except
105
+ # initialize. This allows methods like :call to be chained.
106
+ #
107
+ # IMPORTANT: No methods other than #__chain, #__as, or #__end should be
108
+ # called on this object, because they will be chained! Instead, use ===
109
+ # to detect the object's type, for example.
110
+ class BasicReceiver < BasicObject
111
+ include ReceiverMethods
81
112
 
82
113
  undef !=
83
114
  undef ==
@@ -90,12 +121,73 @@ module Hashformer
90
121
  undef singleton_method_undefined
91
122
  end
92
123
 
93
- # Returns the call chaining receiver.
124
+ # Debuggable chain receiver that inherits from Object. This will break a
125
+ # lot of chains (e.g. any chain using #to_s or #inspect), but will allow
126
+ # some debugging tools to operate without crashing. See
127
+ # Hashformer::Generate::Chain.enable_debugging.
128
+ class DebuggableReceiver
129
+ include ReceiverMethods
130
+
131
+ # Overrides ReceiverMethods#method_missing to print out methods as they
132
+ # are added to the chain.
133
+ def method_missing(name, *args, &block)
134
+ __dbg_msg(name, args, block)
135
+ super
136
+ end
137
+
138
+ # Overrides ReceiverMethods#__as to print out blocks as they are added
139
+ # to the chain.
140
+ def __as(*args, &block)
141
+ __dbg_msg('__as', args, block)
142
+ super
143
+ end
144
+
145
+ # Overrides ReceiverMethods#__end to print a message when a chain is
146
+ # ended.
147
+ def __end
148
+ $stdout.puts "Ending chain #{__id__}"
149
+ super
150
+ end
151
+
152
+ private
153
+
154
+ # Prints a debugging message for the addition of the given method
155
+ # +name+, +args+, and +block+. Prints "Adding..." for active chains,
156
+ # "Ignoring..." for ended chains.
157
+ def __dbg_msg(name, args, block)
158
+ $stdout.puts "#{@__ended ? 'Ignoring' : 'Adding'} " \
159
+ "#{name.inspect}(#{args.map(&:inspect).join(', ')}){#{block}} " \
160
+ "to chain #{__id__}"
161
+ end
162
+ end
163
+
164
+ class << self
165
+ # The chaining receiver class that will be used by newly created chains
166
+ # (must include ReceiverMethods).
167
+ def receiver_class
168
+ @receiver_class ||= BasicReceiver
169
+ end
170
+
171
+ # Switches Receiver to an Object (DebuggableReceiver) for debugging.
172
+ # debugging tools to introspect Receiver without crashing.
173
+ def enable_debugging
174
+ @receiver_class = DebuggableReceiver
175
+ end
176
+
177
+ # Switches Receiver back to a BasicObject (BasicReceiver).
178
+ def disable_debugging
179
+ @receiver_class = BasicReceiver
180
+ end
181
+ end
182
+
183
+
184
+ # Returns the call chaining receiver for this chain.
94
185
  attr_reader :receiver
95
186
 
187
+ # Initializes an empty chain.
96
188
  def initialize
97
189
  @calls = []
98
- @receiver = Receiver.new
190
+ @receiver = self.class.receiver_class.new
99
191
  @receiver.__chain = self
100
192
  end
101
193
 
@@ -103,12 +195,17 @@ module Hashformer
103
195
  def call(input_hash)
104
196
  value = input_hash
105
197
  @calls.each do |c|
106
- value = value.send(c[:name], *c[:args], &c[:block])
198
+ if c[:name]
199
+ value = value.send(c[:name], *c[:args], &c[:block])
200
+ else
201
+ # Support #__as
202
+ value = c[:block].call(value, *c[:args])
203
+ end
107
204
  end
108
205
  value
109
206
  end
110
207
 
111
- # Adds the given call info (used by Receiver).
208
+ # Adds the given call info (used by ReceiverMethods).
112
209
  def <<(info)
113
210
  @calls << info
114
211
  self
@@ -179,6 +276,8 @@ module Hashformer
179
276
  # transformation. This allows path references (as with HF::G.path) and
180
277
  # method calls to be stored and applied later.
181
278
  #
279
+ # See Hashformer::Generate::Chain.enable_debugging if you run into issues.
280
+ #
182
281
  # Example:
183
282
  # data = { in1: { in2: [1, 2, 3, [4, 5, 6, 7]] } }
184
283
  # xform = { out1: HF::G.chain[:in1][:in2][3].reduce(&:+) }
@@ -1,3 +1,3 @@
1
1
  module Hashformer
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/hashformer.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  # Hashformer: A declarative data transformation DSL for Ruby
2
2
  # Created June 2014 by Mike Bourgeous, DeseretBook.com
3
- # Copyright (C)2014 Deseret Book
3
+ # Copyright (C)2016 Deseret Book
4
4
  # See LICENSE and README.md for details.
5
5
 
6
6
  require 'classy_hash'
7
7
 
8
8
  require 'hashformer/version'
9
9
  require 'hashformer/generate'
10
+ require 'hashformer/date'
10
11
 
11
12
 
12
13
  # This module contains the Hashformer methods for transforming Ruby Hash
@@ -47,7 +48,7 @@ module Hashformer
47
48
  next if key == :__in_schema || key == :__out_schema
48
49
 
49
50
  key = key.call(value, data) if key.respond_to?(:call)
50
- out[key] = self.get_value(data, value, xform)
51
+ out[key] = self.get_value(data, value)
51
52
  end
52
53
 
53
54
  validate(out, xform[:__out_schema], 'output') if validate
@@ -56,10 +57,10 @@ module Hashformer
56
57
  end
57
58
 
58
59
  # Returns a value for the given +key+, method chain, or callable on the given
59
- # +input_hash+. If +xform+ is not nil, then Hashe keys will be processed
60
- # with Hashformer.transform.
61
- def self.get_value(input_hash, key, xform = nil)
62
- if Hashformer::Generate::Chain::Receiver === key
60
+ # +input_hash+. Hash keys will be processed with Hashformer.transform for
61
+ # supporting nested transformations.
62
+ def self.get_value(input_hash, key)
63
+ if Hashformer::Generate::Chain::ReceiverMethods === key
63
64
  # Had to special case chains to allow chaining .call
64
65
  key.__chain.call(input_hash)
65
66
  elsif Hashformer::Generate::Constant === key
@@ -0,0 +1,96 @@
1
+ # Hashformer date helper tests
2
+ # Created July 2016 by Mike Bourgeous, DeseretBook.com
3
+ # Copyright (C)2016 Deseret Book
4
+ # See LICENSE and README.md for details.
5
+
6
+ require 'rational'
7
+ require 'active_support/time'
8
+
9
+ describe Hashformer::Date do
10
+ let(:data) {
11
+ {
12
+ one_time: Time.at(1),
13
+ little_time: Time.at(0.25),
14
+ no_time: nil,
15
+
16
+ one_stamp: 1,
17
+ little_stamp: 0.25,
18
+ rational_stamp: Rational(3, 4),
19
+ no_stamp: nil,
20
+ }
21
+ }
22
+
23
+ describe '.to_i' do
24
+ let(:xform) {
25
+ {
26
+ one: HF::Date.to_i(:one_time),
27
+ zero: HF::Date.to_i(:little_time),
28
+ none: HF::Date.to_i(:no_time),
29
+ }
30
+ }
31
+
32
+ it 'converts dates to integer timestamps' do
33
+ expect(HF.transform(data, xform)).to eq({ one: 1, zero: 0, none: nil })
34
+ end
35
+
36
+ it 'raises an error for invalid types' do
37
+ expect{HF.transform({ one_time: 'Bogus' }, xform)}.to raise_error(/Invalid/)
38
+ end
39
+ end
40
+
41
+ describe '.to_f' do
42
+ let(:xform) {
43
+ {
44
+ one: HF::Date.to_f(:one_time),
45
+ some: HF::Date.to_f(:little_time),
46
+ none: HF::Date.to_f(:no_time),
47
+ }
48
+ }
49
+
50
+ it 'converts dates to float timestamps' do
51
+ expect(HF.transform(data, xform)).to eq({ one: 1.0, some: 0.25, none: nil })
52
+ end
53
+
54
+ it 'raises an error for invalid types' do
55
+ expect{HF.transform({ one_time: 'Bogus' }, xform)}.to raise_error(/Invalid/)
56
+ end
57
+ end
58
+
59
+ describe '.to_date' do
60
+ let(:xform) {
61
+ {
62
+ one_utc: HF::Date.to_date(:one_stamp),
63
+ little_utc: HF::Date.to_date(:little_stamp),
64
+ rational_utc: HF::Date.to_date(:rational_stamp),
65
+
66
+ one_local: HF::Date.to_date(:one_stamp, nil),
67
+ little_local: HF::Date.to_date(:little_stamp, nil),
68
+ rational_local: HF::Date.to_date(:rational_stamp, nil),
69
+
70
+ one_zone: HF::Date.to_date(:one_stamp, 'MST'),
71
+ little_zone: HF::Date.to_date(:little_stamp, 'MST'),
72
+ rational_zone: HF::Date.to_date(:rational_stamp, 'MST'),
73
+ }
74
+ }
75
+
76
+ let(:expected) {
77
+ {
78
+ one_utc: Time.at(1).utc.to_datetime,
79
+ little_utc: Time.at(0.25).utc.to_datetime,
80
+ rational_utc: Time.at(Rational(3, 4)).utc.to_datetime,
81
+
82
+ one_local: Time.at(1).to_datetime,
83
+ little_local: Time.at(0.25).to_datetime,
84
+ rational_local: Time.at(Rational(3, 4)).to_datetime,
85
+
86
+ one_zone: Time.at(1).in_time_zone('MST').to_datetime,
87
+ little_zone: Time.at(0.25).in_time_zone('MST').to_datetime,
88
+ rational_zone: Time.at(Rational(3, 4)).in_time_zone('MST').to_datetime,
89
+ }
90
+ }
91
+
92
+ it 'converts timestamps to expected dates' do
93
+ expect(HF.transform(data, xform)).to eq(expected)
94
+ end
95
+ end
96
+ end
@@ -1,18 +1,10 @@
1
1
  # Hashformer transformation generator tests
2
2
  # Created July 2014 by Mike Bourgeous, DeseretBook.com
3
- # Copyright (C)2014 Deseret Book
3
+ # Copyright (C)2016 Deseret Book
4
4
  # See LICENSE and README.md for details.
5
5
 
6
- require 'spec_helper'
7
-
8
- require 'hashformer'
9
-
10
- RSpec.describe Hashformer::Generate do
6
+ describe Hashformer::Generate do
11
7
  describe '.const' do
12
- let(:data) {
13
- {}
14
- }
15
-
16
8
  it 'returns the original integer when given an integer' do
17
9
  expect(Hashformer.transform({}, { a: HF::G.const(5) })).to eq({a: 5})
18
10
  end
@@ -228,6 +220,65 @@ RSpec.describe Hashformer::Generate do
228
220
  expect(Hashformer.transform(data, xform)).to eq({out1: 22, out2: 'd', out3: 1})
229
221
  end
230
222
 
223
+ it 'does not add more chained methods when Chain#inspect is called' do
224
+ chain = HF[:test].one(1).two(2).three(3).four(4).five(5).__chain
225
+ inspect = chain.inspect
226
+ expect(inspect).to match(/one.*two.*three.*four.*five/)
227
+ expect(chain.inspect).to eq(inspect)
228
+ expect(chain.inspect).to eq(inspect)
229
+ end
230
+
231
+ describe '.__as' do
232
+ it 'returns the value from the block' do
233
+ xf = { out1: HF[:in1][:in1][0].__as{|v| "H#{v}shformer" } }
234
+ expect(Hashformer.transform(data, xf)).to eq({ out1: 'Hashformer' })
235
+ end
236
+
237
+ it 'raises an error if no block is given' do
238
+ expect{ HF[].__as() }.to raise_error(/No block given/)
239
+ end
240
+ end
241
+
242
+ describe '.__end' do
243
+ it 'prevents further method calls or __as blocks from being added' do
244
+ xf = { out1: HF[:in1][:in1].count.__end.odd?.__as{nil}.__end.no.more.calls.added }
245
+ expect(Hashformer.transform(data, xf)).to eq({ out1: 4 })
246
+ end
247
+ end
248
+
249
+ context 'debugging methods' do
250
+ it 'can enable and disable debugging' do
251
+ begin
252
+ HF::G::Chain.enable_debugging
253
+
254
+ chain = HF[]
255
+
256
+ expect($stdout).to receive(:puts).with(/Adding.*__as/)
257
+ chain.__as{}
258
+
259
+ expect($stdout).to receive(:puts).with(/Adding.*info/)
260
+ chain.info
261
+
262
+ expect($stdout).to receive(:puts).with(/Ending/)
263
+ chain.__end
264
+
265
+ expect($stdout).to receive(:puts).with(/Ignoring.*__as/)
266
+ chain.__as{}
267
+
268
+ expect($stdout).to receive(:puts).with(/Ignoring.*info/)
269
+ chain.info
270
+
271
+
272
+ HF::G::Chain.disable_debugging
273
+
274
+ expect($stdout).not_to receive(:puts)
275
+ HF[].add.__as{}.and.some.methods.then.__end.the.chain
276
+ ensure
277
+ HF::G::Chain.disable_debugging
278
+ end
279
+ end
280
+ end
281
+
231
282
  context 'using normally reserved methods' do
232
283
  it 'calls a proc with .call' do
233
284
  calldata = {
@@ -3,11 +3,7 @@
3
3
  # Copyright (C)2014 Deseret Book
4
4
  # See LICENSE and README.md for details.
5
5
 
6
- require 'spec_helper'
7
-
8
- require 'hashformer'
9
-
10
- RSpec.describe Hashformer do
6
+ describe Hashformer do
11
7
  describe '.validate' do
12
8
  let(:in_schema) {
13
9
  # ClassyHash schema - https://github.com/deseretbook/classy_hash
data/spec/spec_helper.rb CHANGED
@@ -3,3 +3,5 @@ require 'simplecov'
3
3
  require 'codeclimate-test-reporter'
4
4
  SimpleCov.start
5
5
  CodeClimate::TestReporter.start
6
+
7
+ require 'hashformer'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hashformer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Deseret Book
@@ -9,42 +9,112 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-08-30 00:00:00.000000000 Z
12
+ date: 2016-07-27 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: classy_hash
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.1'
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.1.1
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '0.1'
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.1.1
14
34
  - !ruby/object:Gem::Dependency
15
35
  name: bundler
16
36
  requirement: !ruby/object:Gem::Requirement
17
37
  requirements:
18
38
  - - "~>"
19
39
  - !ruby/object:Gem::Version
20
- version: '1.5'
40
+ version: '1.11'
21
41
  type: :development
22
42
  prerelease: false
23
43
  version_requirements: !ruby/object:Gem::Requirement
24
44
  requirements:
25
45
  - - "~>"
26
46
  - !ruby/object:Gem::Version
27
- version: '1.5'
47
+ version: '1.11'
28
48
  - !ruby/object:Gem::Dependency
29
- name: classy_hash
49
+ name: rspec
30
50
  requirement: !ruby/object:Gem::Requirement
31
51
  requirements:
32
52
  - - "~>"
33
53
  - !ruby/object:Gem::Version
34
- version: '0.1'
35
- - - ">="
54
+ version: '3.5'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
36
60
  - !ruby/object:Gem::Version
37
- version: 0.1.1
38
- type: :runtime
61
+ version: '3.5'
62
+ - !ruby/object:Gem::Dependency
63
+ name: simplecov
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.12.0
69
+ type: :development
39
70
  prerelease: false
40
71
  version_requirements: !ruby/object:Gem::Requirement
41
72
  requirements:
42
73
  - - "~>"
43
74
  - !ruby/object:Gem::Version
44
- version: '0.1'
75
+ version: 0.12.0
76
+ - !ruby/object:Gem::Dependency
77
+ name: codeclimate-test-reporter
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
45
80
  - - ">="
46
81
  - !ruby/object:Gem::Version
47
- version: 0.1.1
82
+ version: '0'
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ - !ruby/object:Gem::Dependency
91
+ name: byebug
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '9.0'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '9.0'
104
+ - !ruby/object:Gem::Dependency
105
+ name: activesupport
106
+ requirement: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.2'
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.2'
48
118
  description: |2
49
119
  Hashformer provides a simple, Ruby Hash-based way of transforming data from
50
120
  one format to another. It's vaguely like XSLT, but way less complicated
@@ -57,14 +127,17 @@ extra_rdoc_files: []
57
127
  files:
58
128
  - ".gitignore"
59
129
  - ".rspec"
130
+ - ".travis.yml"
60
131
  - Gemfile
61
132
  - LICENSE
62
133
  - README.md
63
134
  - Rakefile
64
135
  - hashformer.gemspec
65
136
  - lib/hashformer.rb
137
+ - lib/hashformer/date.rb
66
138
  - lib/hashformer/generate.rb
67
139
  - lib/hashformer/version.rb
140
+ - spec/lib/hashformer/date_spec.rb
68
141
  - spec/lib/hashformer/generate_spec.rb
69
142
  - spec/lib/hashformer_spec.rb
70
143
  - spec/spec_helper.rb
@@ -79,7 +152,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
152
  requirements:
80
153
  - - ">="
81
154
  - !ruby/object:Gem::Version
82
- version: 1.9.3
155
+ version: '2.0'
83
156
  required_rubygems_version: !ruby/object:Gem::Requirement
84
157
  requirements:
85
158
  - - ">="
@@ -87,11 +160,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
160
  version: '0'
88
161
  requirements: []
89
162
  rubyforge_project:
90
- rubygems_version: 2.2.2
163
+ rubygems_version: 2.5.1
91
164
  signing_key:
92
165
  specification_version: 4
93
166
  summary: Transform any Hash with a declarative data transformation DSL for Ruby
94
167
  test_files:
168
+ - spec/lib/hashformer/date_spec.rb
95
169
  - spec/lib/hashformer/generate_spec.rb
96
170
  - spec/lib/hashformer_spec.rb
97
171
  - spec/spec_helper.rb