enumpath 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f0c82457a7964b42629b6a3ea752d1cb9a3a383b41fde79d9fd249f25a73cdbf
4
+ data.tar.gz: 57353f0105eb540fc1bf9202811b80c5afba4c6268e00e5fa9d644521fc53911
5
+ SHA512:
6
+ metadata.gz: 1c235bf4d538e646697f21f0411f1f4bf74e982957e1092dd05d41f6363bf37fd7bea5a74c3bca223f2e0521bad82f637f89b8eaff4332ae1354c546986e7af7
7
+ data.tar.gz: 39d33c4b50f464dfc8de6fe1b5b0d3edd8f528ca1714ff0b69e767aa09c5a4b13211ee78c9d07aad2e5c2f98e450a23c792bca915c2ddcafe5cf57831e1b243b
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+
13
+ .byebug_history
14
+ .ruby-gemset
15
+ .ruby-version
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.5.1
5
+ before_install: gem install bundler -v 1.16.1
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private - LICENSE
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,91 @@
1
+ # Contributing
2
+
3
+ Thanks for being interested in our project! We welcome [all kinds of contributions](https://opensource.guide/how-to-contribute/). There are a few things you should know before contributing to this repository:
4
+
5
+ 1. All contributors must agree to our Contributor License Agreement before any merge requests will be accepted.
6
+ 2. Please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.
7
+ 3. Please note we have a code of conduct, please follow it in all your interactions with the project.
8
+
9
+ ## Pull Request Process
10
+
11
+ 1. Fork the repo.
12
+ 2. Make sure the tests pass before you begin working.
13
+ 3. Make your changes, with new passing tests. Follow the [Ruby Style Guide](https://github.com/rubocop-hq/ruby-style-guide).
14
+ 4. Push to your fork. Write a [good commit message](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
15
+ 5. Submit a [good pull request](https://blog.github.com/2015-01-21-how-to-write-the-perfect-pull-request/).
16
+ 6. Others will give constructive feedback. This is a time for discussion and improvements, and making the necessary changes will be required before we can merge the contribution.
17
+
18
+ ## Code of Conduct
19
+
20
+ ### Our Pledge
21
+
22
+ In the interest of fostering an open and welcoming environment, we as
23
+ contributors and maintainers pledge to making participation in our project and
24
+ our community a harassment-free experience for everyone, regardless of age, body
25
+ size, disability, ethnicity, sex characteristics, gender identity and expression,
26
+ level of experience, education, socio-economic status, nationality, personal
27
+ appearance, race, religion, or sexual identity and orientation.
28
+
29
+ ### Our Standards
30
+
31
+ Examples of behavior that contributes to creating a positive environment
32
+ include:
33
+
34
+ * Using welcoming and inclusive language
35
+ * Being respectful of differing viewpoints and experiences
36
+ * Gracefully accepting constructive criticism
37
+ * Focusing on what is best for the community
38
+ * Showing empathy towards other community members
39
+
40
+ Examples of unacceptable behavior by participants include:
41
+
42
+ * The use of sexualized language or imagery and unwelcome sexual attention or
43
+ advances
44
+ * Trolling, insulting/derogatory comments, and personal or political attacks
45
+ * Public or private harassment
46
+ * Publishing others' private information, such as a physical or electronic
47
+ address, without explicit permission
48
+ * Other conduct which could reasonably be considered inappropriate in a
49
+ professional setting
50
+
51
+ ### Our Responsibilities
52
+
53
+ Project maintainers are responsible for clarifying the standards of acceptable
54
+ behavior and are expected to take appropriate and fair corrective action in
55
+ response to any instances of unacceptable behavior.
56
+
57
+ Project maintainers have the right and responsibility to remove, edit, or
58
+ reject comments, commits, code, wiki edits, issues, and other contributions
59
+ that are not aligned to this Code of Conduct, or to ban temporarily or
60
+ permanently any contributor for other behaviors that they deem inappropriate,
61
+ threatening, offensive, or harmful.
62
+
63
+ ### Scope
64
+
65
+ This Code of Conduct applies both within project spaces and in public spaces
66
+ when an individual is representing the project or its community. Examples of
67
+ representing a project or community include using an official project e-mail
68
+ address, posting via an official social media account, or acting as an appointed
69
+ representative at an online or offline event. Representation of a project may be
70
+ further defined and clarified by project maintainers.
71
+
72
+ ### Enforcement
73
+
74
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
75
+ reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
76
+ complaints will be reviewed and investigated and will result in a response that
77
+ is deemed necessary and appropriate to the circumstances. The project team is
78
+ obligated to maintain confidentiality with regard to the reporter of an incident.
79
+ Further details of specific enforcement policies may be posted separately.
80
+
81
+ Project maintainers who do not follow or enforce the Code of Conduct in good
82
+ faith may face temporary or permanent repercussions as determined by other
83
+ members of the project's leadership.
84
+
85
+ ### Attribution
86
+
87
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
88
+ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
89
+
90
+ [homepage]: https://www.contributor-covenant.org
91
+ [version]: http://contributor-covenant.org/version/1/4/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in enumpath.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,56 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ enumpath (0.1.0)
5
+ mini_cache (~> 1.1.0)
6
+ to_regexp (~> 0.2.1)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ benchmark-perf (0.2.1)
12
+ byebug (10.0.2)
13
+ coderay (1.1.2)
14
+ diff-lcs (1.3)
15
+ method_source (0.9.0)
16
+ mini_cache (1.1.0)
17
+ null-logger (0.1.5)
18
+ pry (0.11.3)
19
+ coderay (~> 1.1.0)
20
+ method_source (~> 0.9.0)
21
+ pry-byebug (3.6.0)
22
+ byebug (~> 10.0)
23
+ pry (~> 0.10)
24
+ rake (12.3.1)
25
+ rspec (3.8.0)
26
+ rspec-core (~> 3.8.0)
27
+ rspec-expectations (~> 3.8.0)
28
+ rspec-mocks (~> 3.8.0)
29
+ rspec-benchmark (0.3.0)
30
+ benchmark-perf (~> 0.2.0)
31
+ rspec (>= 3.0.0, < 4.0.0)
32
+ rspec-core (3.8.0)
33
+ rspec-support (~> 3.8.0)
34
+ rspec-expectations (3.8.1)
35
+ diff-lcs (>= 1.2.0, < 2.0)
36
+ rspec-support (~> 3.8.0)
37
+ rspec-mocks (3.8.0)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.8.0)
40
+ rspec-support (3.8.0)
41
+ to_regexp (0.2.1)
42
+
43
+ PLATFORMS
44
+ ruby
45
+
46
+ DEPENDENCIES
47
+ bundler (~> 1.16)
48
+ enumpath!
49
+ null-logger (~> 0.1)
50
+ pry-byebug (~> 3.6)
51
+ rake (~> 12.3)
52
+ rspec (~> 3.8)
53
+ rspec-benchmark (~> 0.3.0)
54
+
55
+ BUNDLED WITH
56
+ 1.16.3
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under th
data/README.md ADDED
@@ -0,0 +1,492 @@
1
+ # Enumpath
2
+
3
+ A JSONPath-compatible library for safely navigating nested Ruby objects using path expressions.
4
+
5
+ ## Introduction
6
+
7
+ Enumpath is an implementation of the [JSONPath][jsonpath] spec for Ruby objects, plus some added sugar. It's like Ruby's native `Enumerable#dig` method, but fancier. It is designed for situations where you need to provide a dynamic way of describing a complex path through nested enumerable objects. This makes it exceptionally well suited for flexible ETL (Extract, Transform, Load) processes by allowing you to define paths through your data in a simple, easily readable, easily storable syntax.
8
+
9
+ Enumpath path expressions look like this:
10
+
11
+ ```
12
+ $.pets.cats.0.name
13
+
14
+ $.pets[cats,dogs].*.name
15
+
16
+ pets..name
17
+
18
+ ['pets']..[?(@.age > 10)].name
19
+
20
+ ..age
21
+
22
+ pets.cats.-1
23
+ ```
24
+
25
+ Enumpath has the following benefits over vanilla `Enumerable#dig`:
26
+
27
+ - Paths can be described as simple strings
28
+ - It's smart enough to figure out which path segments are integer indexes versus symbolic keys versus string keys
29
+ - It enables the use of wildcard, recursive descent, filter, subscript, union, and slice operators to describe complex paths through the data
30
+
31
+ Like `Enumerable#dig`, Enumpath protects against missing path segments and returns safely if the full path cannot be resolved.
32
+
33
+ ## Installation
34
+
35
+ Add this line to your application's Gemfile:
36
+
37
+ ```ruby
38
+ gem 'enumpath'
39
+ ```
40
+
41
+ And then execute:
42
+
43
+ $ bundle
44
+
45
+ Or install it yourself as:
46
+
47
+ $ gem install enumpath
48
+
49
+ ## Usage
50
+
51
+ Enumpath exposes a simple interface via `Enumpath.apply` that takes a path and an enumerable.
52
+
53
+ ```ruby
54
+ party = { food: %w(pizza tacos) }
55
+ Enumpath.apply('food.0', party) # => ["pizza"]
56
+ ```
57
+
58
+ The result of `Enumpath.apply` is an array of values that were extracted according to the path. Technically it's an instance of `Enumpath::Results` which is an Array-like object that allows you to chain further calls to `.apply` like this:
59
+
60
+ ```ruby
61
+ party = { food: %w(pizza tacos) }
62
+ results = Enumpath.apply('food.*', party) # => ["pizza", "tacos"]
63
+ results.apply("[?(@ == 'pizza')]") # => ["pizza"]
64
+ ```
65
+
66
+ In the event that the path doesn't match anything, an empty results set is returned:
67
+
68
+ ```ruby
69
+ party = { food: %w(pizza tacos) }
70
+ Enumpath.apply("drinks.*", party) # => []
71
+ ```
72
+
73
+ > This is a thoughtful deviation from the original JSONPath spec which would return `false` on no matches.
74
+
75
+ ## Operator Reference
76
+
77
+ Enumpath currently implements the following path operators:
78
+
79
+ operator | summary | basic examples
80
+ :---: | --- | ---
81
+ `$` | **Root**; only valid at the beginning of a path and it is entirely optional | `$.puppies` is equivalent to `puppies`
82
+ `.` or `[]` | **[Child](#child-operator)**; can be dot notation or bracket, and bracketed child operators can optionally be wrapped in single quotes | `locations`, `0`, `[departments]`, and `['human resources']` are all valid child operators
83
+ `*` | **[Wildcard](#wildcard-operator)**; applies the remaining path to each member of the current enumerable | `children.*.name` would result in an array containing the names of all the children
84
+ `..` | **[Recursive descent](#recursive-descent-operator)**; applies the remaining path to all members at every level of the current enumerable, including to the current enumerable itself | `..name` would find all the `name` members all the way down through the enumerable regardless of nesting level
85
+ `[start:end:step]` | **[Slice](#slice-operator)**; similar in functionality to Ruby's `Array#slice` method with the addition of a step argument | `[1:8:2]` would operate on indices 1, 3, 5, and 7
86
+ `[child1,child2[,...]]` | **[Union](#union-operator)**; combines the results from multiple child operators | `authors.*[first_name,last_name]` is equivalent to `authors.*.first_name` + `authors.*.last_name`
87
+ `?()` | **[Filter expression](#filter-expression-operator)**; evaluates boolean expressions against the current enumerable; only the members of enumerable that meet the criteria are passed through | `[?(@.price > 10 && @.price <= 20 )]` would return all items whose price is greater than 10 and less than or equal to 20
88
+ `()` | **[Subscript expression](#subscript-expression-operator)**; evaluates an expression as a subscript on the current enumerator | `[(@.length - 1)]` would apply a child operator equal to the `#length` of the current enumerable minus 1 to the current enumerable (i.e. the last member of an array)
89
+
90
+ ### Child operator
91
+
92
+ Syntax: `child` or `[child]` or `['child']`
93
+
94
+ Child operators match on an index, key, member, or property of the enumerable. In its non-normalized form a child operator is preceded by `.` or wrapped in '[]'. In bracket notation the child may optionally be wrapped in single quotes. Enumpath will attempt to resolve the data type of the child operator in the following order of precedence:
95
+
96
+ 1. as an integer key or index (if the segment is integer-like),
97
+ 2. then as a string key,
98
+ 3. then as a symbol key,
99
+ 4. and finally as a public property (i.e. a public method of the target that expects no arguments)
100
+
101
+ #### Examples
102
+
103
+ ```
104
+ Car = Struct.new(:color, :transmition, :owners)
105
+ hiundai = Car.new('blue', :automatic, [{ name: 'Bill' }, { name: 'Ted' }])
106
+ subaru = Car.new('gold', :standard, [{ name: 'Kate' }])
107
+ jeep = Car.new('black', :automatic, [])
108
+ garages = [{ 'cars' => [hiundai, subaru] }, { 'cars' => [jeep] }]
109
+ Enumpath.apply('1', garages) # => [{"cars"=>[#<struct Car color="black", transmition=:automatic, owners=[]>]}]
110
+ Enumpath.apply('0.cars.-1', garages) # => [#<struct Car color="gold", transmition=:standard, owners=[{:name=>"Kate"}]>]
111
+ Enumpath.apply('1.cars.0.owners.length', garages) # => [0]
112
+ ```
113
+
114
+ ### Wildcard operator
115
+
116
+ Syntax: `*` or `[*]`
117
+
118
+ Wildcards match each immediate member of the enumerable.
119
+
120
+ ### Recursive descent operator
121
+
122
+ Syntax: `..`
123
+
124
+ Applies the remaining path expression segments recursively to all members of the enumerable regardless of their nesting level, including the enumerable itself.
125
+
126
+ ### Slice operator
127
+
128
+ Syntax: `[start:end:step]`, `[start:]`, `[start:end]`, `[start::step]`, `[:end]`, `[:end:step]`, `[::step]`, ...
129
+
130
+ The slice operator selects a range of elements like Ruby's _`start...end`_ literal, excluding the end value, and then selects each _step_ items. The _start_, _end_, and _step_ arguments default to `0`, `Enumerable#length`, and `1` respectively. The remaining path expression segments are passed to each member whose index is included by the slice operator.
131
+
132
+ The operator accepts a mixed bag of argument combinations. For instance, these are all valid slice operators:
133
+
134
+ - `[1:8]`: passes through the members of the enumerable at indices 1 – 7
135
+ - `[1:]`: passes through the members of the enumerable at indices 1 – `Enumerable#length`
136
+ - `[:8]`: passes through the members of the enumerable at indices 0 – 7
137
+ - `[:8:2]`: passes through the members of the enumerable at indices 0, 2, 4, and 6
138
+ - `[::2]`: passes through the members of the enumerable at indices 0, 2, 4, 6, 8, ... up to `Enumerable#length`
139
+
140
+ ### Union operator
141
+
142
+ Syntax: `[child1,child2,...]`
143
+
144
+ The union operator combines the results of two or more child operators. There is no limit to the number of child operators you can specify in a single union. Each child operator is separated by a comma (`,`). White space is stripped from around each child operator. Child operators can optionally be wrapped in single quotes. Bracket notation is not supporter in this context.
145
+
146
+ The following are all valid union operators:
147
+
148
+ - `[first,last]`
149
+ - `[first,middle,last]`
150
+ - `['first', middle , last]`
151
+
152
+ ### Filter expression operator
153
+
154
+ Syntax: `[?(expression)]`, `[?(expression && expression)]`, `[?(expression || expression)]`, ...
155
+
156
+ A filter expression is made up of one or more boolean expressions. Each boolean expression consists of a child operator (`@.child` or `child`; the leading `@.` is optional), plus an optional pair of comparison operator and operand. The comparison operator can be any one of `==`, `!=`, `>=`, `<=`, `<=>`, `>`, `<`, `=~`, `!~`. The operand can be a string (`'some string'`), symbol (`:some_symbol`), boolean constant (`true` or `false`), nil constant (`nil`), regular expression (`/^Some\s+/i`), or numeric value (`10` or `1.0`). If an operator and operand are not included in an expression then the value located at the child operator is evaluated for truthiness. Multiple expression groups can be chained together with `&&` or `||` logical operators, but note that parenthetical grouping of expressions is not supported; the results of each are applied to the previous running result in order. Any member of the current enumerable that passes the net result of the filter expression will be included in further processing of the path.
157
+
158
+ The following are all valid filter expressions:
159
+
160
+ - `[?(@.isbn)]`: any member who has an `isbn` value that is not falsey
161
+ - `[?(isbn)]`: equivalent to the previous example
162
+ - `[?(@.price == 8)]`: any member whose `price` value is equal to 8
163
+ - `[?(@.price == 8 || @.price == 10)]`: members with a `price` of 8 _or_ 10
164
+ - `[?(@.price > 2 && @.price < 10)]`: members with a `price` greater than 8 _and_ less than 10
165
+ - `[?(@.name =~ /bob/i || @.name == 'Mark')]`: any member whose `name` matches the regex `/bob/i` or equals `'Mark'`
166
+
167
+ > Regular expression operands are safely parsed using the `to_regexp` gem
168
+
169
+ ### Subscript expression operator
170
+
171
+ Syntax: `[(expression)]`
172
+
173
+ A subscript expression is made up of a singe expression that consists of a child operator (`@.child` or `child`; the leading `@.` is optional), plus an optional pair of arithmetic operator and operand. The arithmetic operator can be any one of `+`, `-`, `**`, `*`, `/`, or `%`. The operand can be a string (`'some string'`), symbol (`:some_symbol`), or numeric value (`10` or `1.0`). The expression is evaluated and the result becomes the subscript. If an operator and operand are not included in an expression then the value located at the child operator is used as the subscript. If the subscript represents a valid child path for the enumerable, the value of that member will be passed along for further processing of the path.
174
+
175
+ The following are all valid filter expressions:
176
+
177
+ - `[(@.length - 1)]`: the subscript becomes the length of the current enumerable, minus 1
178
+ - `[(length / 2)]`: the subscript becomes the index at half the length of the enumerable
179
+ - `[(@.type)]`: the subscript becomes the value at the `type` key, member, or property of the enumerable
180
+
181
+ ## Examples
182
+
183
+ Given the same store example from the JSONPath project:
184
+
185
+ ```ruby
186
+ store_info = {
187
+ store: {
188
+ book: [
189
+ { category: "reference",
190
+ author: "Nigel Rees",
191
+ title: "Sayings of the Century",
192
+ price: 8.95 },
193
+ { category: "fiction",
194
+ author: "Evelyn Waugh",
195
+ title: "Sword of Honour",
196
+ price: 12.99 },
197
+ { category: "fiction",
198
+ author: "Herman Melville",
199
+ title: "Moby Dick",
200
+ isbn: "0-553-21311-3",
201
+ price: 8.99 },
202
+ { category: "fiction",
203
+ author: "J. R. R. Tolkien",
204
+ title: "The Lord of the Rings",
205
+ isbn: "0-395-19395-8",
206
+ price: 22.99 }
207
+ ],
208
+ bicycle: { color: "red", price: 19.95 }
209
+ }
210
+ }
211
+
212
+ # The authors of all the books in the store
213
+ Enumpath.apply("$.store.book[*].author", store_info)
214
+ # => ["Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"]
215
+
216
+ # All prices in the store
217
+ Enumpath.apply("$..price", store_info)
218
+ # => [8.95, 12.99, 8.99, 22.99, 19.95]
219
+
220
+ # All things in store, which are some books and a red bicycle
221
+ Enumpath.apply("$.store.*", store_info)
222
+ # => [[{:category=>"reference",
223
+ # :author=>"Nigel Rees",
224
+ # :title=>"Sayings of the Century",
225
+ # :price=>8.95},
226
+ # {:category=>"fiction",
227
+ # :author=>"Evelyn Waugh",
228
+ # :title=>"Sword of Honour",
229
+ # :price=>12.99},
230
+ # {:category=>"fiction",
231
+ # :author=>"Herman Melville",
232
+ # :title=>"Moby Dick",
233
+ # :isbn=>"0-553-21311-3",
234
+ # :price=>8.99},
235
+ # {:category=>"fiction",
236
+ # :author=>"J. R. R. Tolkien",
237
+ # :title=>"The Lord of the Rings",
238
+ # :isbn=>"0-395-19395-8",
239
+ # :price=>22.99}],
240
+ # {:color=>"red", :price=>19.95}]
241
+
242
+ # The third book
243
+ Enumpath.apply("$..book[2]", store_info)
244
+ # => [{:category=>"fiction", :author=>"Herman Melville", :title=>"Moby Dick", :isbn=>"0-553-21311-3", :price=>8.99}]
245
+
246
+ # The last book in order
247
+ Enumpath.apply("$..book[(@.length-1)]", store_info)
248
+ Enumpath.apply("$..book[-1:]", store_info)
249
+ # => Both return: [{:category=>"fiction", :author=>"J. R. R. Tolkien", :title=>"The Lord of the Rings", :isbn=>"0-395-19395-8", :price=>22.99}]
250
+
251
+ # The first two books in order. Both of these path expressions are equivalent
252
+ Enumpath.apply("$..book[0,1]", store_info)
253
+ Enumpath.apply("$..book[:2]", store_info)
254
+ # => [{:category=>"reference",
255
+ # :author=>"Nigel Rees",
256
+ # :title=>"Sayings of the Century",
257
+ # :price=>8.95},
258
+ # {:category=>"fiction",
259
+ # :author=>"Evelyn Waugh",
260
+ # :title=>"Sword of Honour",
261
+ # :price=>12.99}]
262
+
263
+ # All books with an isbn number
264
+ Enumpath.apply("$..book[?(@.isbn)]", store_info)
265
+ # => [{:category=>"fiction",
266
+ # :author=>"Herman Melville",
267
+ # :title=>"Moby Dick",
268
+ # :isbn=>"0-553-21311-3",
269
+ # :price=>8.99},
270
+ # {:category=>"fiction",
271
+ # :author=>"J. R. R. Tolkien",
272
+ # :title=>"The Lord of the Rings",
273
+ # :isbn=>"0-395-19395-8",
274
+ # :price=>22.99}]
275
+
276
+ # All books with a price less than 10
277
+ Enumpath.apply("$..book[?(@.price<10)]", store_info)
278
+ # => [{:category=>"reference",
279
+ # :author=>"Nigel Rees",
280
+ # :title=>"Sayings of the Century",
281
+ # :price=>8.95},
282
+ # {:category=>"fiction",
283
+ # :author=>"Herman Melville",
284
+ # :title=>"Moby Dick",
285
+ # :isbn=>"0-553-21311-3",
286
+ # :price=>8.99}]
287
+
288
+ # All members of the enumerable, recursively
289
+ Enumpath.apply("$..*", store_info)
290
+ # => [{:book=>
291
+ # [{:category=>"reference",
292
+ # :author=>"Nigel Rees",
293
+ # :title=>"Sayings of the Century",
294
+ # :price=>8.95},
295
+ # {:category=>"fiction",
296
+ # :author=>"Evelyn Waugh",
297
+ # :title=>"Sword of Honour",
298
+ # :price=>12.99},
299
+ # {:category=>"fiction",
300
+ # :author=>"Herman Melville",
301
+ # :title=>"Moby Dick",
302
+ # :isbn=>"0-553-21311-3",
303
+ # :price=>8.99},
304
+ # {:category=>"fiction",
305
+ # :author=>"J. R. R. Tolkien",
306
+ # :title=>"The Lord of the Rings",
307
+ # :isbn=>"0-395-19395-8",
308
+ # :price=>22.99}],
309
+ # :bicycle=>{:color=>"red", :price=>19.95}},
310
+ # [{:category=>"reference",
311
+ # :author=>"Nigel Rees",
312
+ # :title=>"Sayings of the Century",
313
+ # :price=>8.95},
314
+ # {:category=>"fiction",
315
+ # :author=>"Evelyn Waugh",
316
+ # :title=>"Sword of Honour",
317
+ # :price=>12.99},
318
+ # {:category=>"fiction",
319
+ # :author=>"Herman Melville",
320
+ # :title=>"Moby Dick",
321
+ # :isbn=>"0-553-21311-3",
322
+ # :price=>8.99},
323
+ # {:category=>"fiction",
324
+ # :author=>"J. R. R. Tolkien",
325
+ # :title=>"The Lord of the Rings",
326
+ # :isbn=>"0-395-19395-8",
327
+ # :price=>22.99}],
328
+ # {:color=>"red", :price=>19.95},
329
+ # {:category=>"reference",
330
+ # :author=>"Nigel Rees",
331
+ # :title=>"Sayings of the Century",
332
+ # :price=>8.95},
333
+ # {:category=>"fiction",
334
+ # :author=>"Evelyn Waugh",
335
+ # :title=>"Sword of Honour",
336
+ # :price=>12.99},
337
+ # {:category=>"fiction",
338
+ # :author=>"Herman Melville",
339
+ # :title=>"Moby Dick",
340
+ # :isbn=>"0-553-21311-3",
341
+ # :price=>8.99},
342
+ # {:category=>"fiction",
343
+ # :author=>"J. R. R. Tolkien",
344
+ # :title=>"The Lord of the Rings",
345
+ # :isbn=>"0-395-19395-8",
346
+ # :price=>22.99},
347
+ # "reference",
348
+ # "Nigel Rees",
349
+ # "Sayings of the Century",
350
+ # 8.95,
351
+ # "fiction",
352
+ # "Evelyn Waugh",
353
+ # "Sword of Honour",
354
+ # 12.99,
355
+ # "fiction",
356
+ # "Herman Melville",
357
+ # "Moby Dick",
358
+ # "0-553-21311-3",
359
+ # 8.99,
360
+ # "fiction",
361
+ # "J. R. R. Tolkien",
362
+ # "The Lord of the Rings",
363
+ # "0-395-19395-8",
364
+ # 22.99,
365
+ # "red",
366
+ # 19.95]
367
+ ```
368
+
369
+ ## Options
370
+
371
+ ### :return_type
372
+
373
+ By default, Enumpath returns the values that match the path expression. Like the original JSONPath implementation, Enumpath also supports returning path results instead of values. This can be useful for collecting static paths from dynamic paths.
374
+
375
+ ```ruby
376
+ party = { food: %w(pizza tacos) }
377
+ Enumpath.apply("food.*", party, result_type: :path) # => ["$['food'][0]", "$['food'][1]"]
378
+ ```
379
+
380
+ Each returned path is a valid path expression that can be used in calls to `Enumpath.apply`. If you want to be explicit about returning values instead of paths you can specify that with the option `result_type: :value`.
381
+
382
+ ### :verbose
383
+
384
+ Seeing how your path expression is being applied to an enumerable can be helpful in understanding the path expression syntax. Enumpath has a built-in logger to assist with this. It can be enabled by simply passing `verbose: true` as an option on `Enumpath.apply`. By default this will log debugging information to STDOUT, however you can provide your own logger.
385
+
386
+ For example:
387
+
388
+ ```ruby
389
+ Enumpath.logger.logger = ::Logger.new('log/enumpath.log')
390
+ ```
391
+
392
+ Once enabled, it will log debugging information like so:
393
+
394
+ ```
395
+ Enumpath.apply('$.store.book', store_info, verbose: true)
396
+
397
+ --------------------------------------
398
+ Enumpath: Path normalized
399
+ --------------------------------------
400
+ original : $.store.book
401
+ normalized: ["store", "book"]
402
+ --------------------------------------
403
+ Enumpath: Applying
404
+ --------------------------------------
405
+ operator: ["store", "book"]
406
+ to : {:store=>{:book=>[{:category=>"reference", :author...
407
+ --------------------------------------
408
+ Enumpath: Child operator detected
409
+ --------------------------------------
410
+ Enumpath: Applying
411
+ --------------------------------------
412
+ operator: ["book"]
413
+ to : {:book=>[{:category=>"reference", :author=>"Nigel ...
414
+ --------------------------------------
415
+ Enumpath: Child operator detected
416
+ --------------------------------------
417
+ Enumpath: Storing
418
+ --------------------------------------
419
+ resolved_path: ["store", "book"]
420
+ enum : [{:category=>"reference", :author=>"Nigel Rees", :...
421
+ --------------------------------------
422
+ Enumpath: New Result
423
+ --------------------------------------
424
+ result: [{:category=>"reference", :author=>"Nigel Rees", :...
425
+ ```
426
+
427
+ You can also control verbose mode via `Enumpath.verbose = true` and `Enumpath.verbose = false`.
428
+
429
+ ## Path normalization
430
+
431
+ When you give a string path to Enumpath it will automatically normalize it to an array of path segments. You can also pass it an array of path segments to avoid the normalization, for instance if the normalization process is having trouble parsing your path, or you happen to have a pre-normalized path already. For example the path `['pets']..[?(@.age > 10)].name` is represented in normalized form as `['pets', '..', '?(@.age > 10)', 'name']`. For the most part you should stick with string paths and let Enumpath normalize on its own.
432
+
433
+ ### Normalized path caching
434
+
435
+ To save a little bit of time on consecutive calls Enumpath caches the normalized version of each path. This is an implementation detail that can generally be ignored but if you run into trouble with it you can clear the cache with `Enumpath.path_cache.reset`.
436
+
437
+ ## Deviations from the Original JSONPath Spec
438
+
439
+ 1. The JSONPath spec required that `false` be returned when no matches were found, but Enumpath will return an empty result set (`[]`) instead. This is a thoughtful divergence based on the principle of least astonishment and the robustness principle.
440
+
441
+ 2. Enumpath supports relative child indexes, which the original implementation did not support. For instance:
442
+
443
+ # Get the last element. Both are equivalent to `$..book[-1:]`
444
+ Enumpath.apply('$..book.-1', store_info)
445
+ Enumpath.apply('$..book[-1]', store_info)
446
+
447
+ 3. The original implementations of JSONPath allowed unchecked evaluation of filter and subscript expressions. Enumpath limits those expressions to a reasonable subset of operations as detailed in the [Operator Reference](#operator-reference) section and uses `public_send` rather than `eval` to resolve expressions as necessary.
448
+
449
+ 4. The original JSONPath spec did not include support for using logical operators to chain expressions in filter expression operators. This addition was inspired by [Gergely Brautigam's](https://skarlso.github.io/2017/05/28/replace-eval-with-object-send-and-a-parser/) work on [joshbuddy/jsonpath](https://github.com/joshbuddy/jsonpath)
450
+
451
+ ## Requirements
452
+
453
+ Enumpath requires Ruby 2.3.0 or higher.
454
+
455
+ ## Development
456
+
457
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
458
+
459
+ ## Contributing
460
+
461
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/youearnedit/enumpath](). Please read [CONTRIBUTING.md]() for details on our code of conduct, and the process for submitting pull requests to us.
462
+
463
+ ## Versioning
464
+
465
+ We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags](https://github.com/youearnedit/enumpath/tags) on this repository.
466
+
467
+ ## Authors
468
+
469
+ - [Chris Bloom](https://github.com/chrisbloom7) - YouEarnedIt
470
+
471
+ See also the list of [contributors](https://github.com/youearnedit/enumpath/graphs/contributors) who participated in this project.
472
+
473
+ ## License
474
+
475
+ Copyright 2018 YouEarnedIt.com
476
+
477
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
478
+
479
+ [www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
480
+
481
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
482
+
483
+ ## Acknowledgements
484
+
485
+ This project is maintained by the Engineering team at [YouEarnedIt](http://youearnedit.com), an employee engagement and performance metrics platform, headquartered in Austin, TX.
486
+
487
+ Enumpath is based on [Stefan Goessner's JSONPath spec][jsonpath], and was inspired by several similar libraries:
488
+
489
+ - [nickcharlton/keypath-ruby](https://github.com/nickcharlton/keypath-ruby)
490
+ - [joshbuddy/jsonpath](https://github.com/joshbuddy/jsonpath)
491
+
492
+ [jsonpath]: http://goessner.net/articles/JsonPath/