ruby-marc-spec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/build.yml +18 -0
  3. data/.gitignore +388 -0
  4. data/.gitmodules +3 -0
  5. data/.idea/codeStyles/codeStyleConfig.xml +5 -0
  6. data/.idea/go.imports.xml +6 -0
  7. data/.idea/inspectionProfiles/Project_Default.xml +23 -0
  8. data/.idea/marc_spec.iml +102 -0
  9. data/.idea/misc.xml +6 -0
  10. data/.idea/modules.xml +8 -0
  11. data/.idea/templateLanguages.xml +6 -0
  12. data/.idea/vcs.xml +7 -0
  13. data/.rubocop.yml +269 -0
  14. data/.ruby-version +1 -0
  15. data/.simplecov +8 -0
  16. data/CHANGES.md +3 -0
  17. data/Gemfile +6 -0
  18. data/LICENSE.md +21 -0
  19. data/README.md +172 -0
  20. data/Rakefile +20 -0
  21. data/lib/.rubocop.yml +5 -0
  22. data/lib/marc/spec/module_info.rb +14 -0
  23. data/lib/marc/spec/parsing/closed_int_range.rb +28 -0
  24. data/lib/marc/spec/parsing/closed_lc_alpha_range.rb +28 -0
  25. data/lib/marc/spec/parsing/parser.rb +213 -0
  26. data/lib/marc/spec/parsing.rb +1 -0
  27. data/lib/marc/spec/queries/al_num_range.rb +105 -0
  28. data/lib/marc/spec/queries/applicable.rb +18 -0
  29. data/lib/marc/spec/queries/character_spec.rb +81 -0
  30. data/lib/marc/spec/queries/comparison_string.rb +45 -0
  31. data/lib/marc/spec/queries/condition.rb +133 -0
  32. data/lib/marc/spec/queries/condition_context.rb +49 -0
  33. data/lib/marc/spec/queries/dsl.rb +80 -0
  34. data/lib/marc/spec/queries/indicator_value.rb +77 -0
  35. data/lib/marc/spec/queries/operator.rb +129 -0
  36. data/lib/marc/spec/queries/part.rb +63 -0
  37. data/lib/marc/spec/queries/position.rb +59 -0
  38. data/lib/marc/spec/queries/position_or_range.rb +27 -0
  39. data/lib/marc/spec/queries/query.rb +94 -0
  40. data/lib/marc/spec/queries/query_executor.rb +52 -0
  41. data/lib/marc/spec/queries/selector.rb +12 -0
  42. data/lib/marc/spec/queries/subfield.rb +88 -0
  43. data/lib/marc/spec/queries/subfield_value.rb +63 -0
  44. data/lib/marc/spec/queries/tag.rb +107 -0
  45. data/lib/marc/spec/queries/transform.rb +154 -0
  46. data/lib/marc/spec/queries.rb +1 -0
  47. data/lib/marc/spec.rb +32 -0
  48. data/rakelib/.rubocop.yml +19 -0
  49. data/rakelib/bundle.rake +8 -0
  50. data/rakelib/coverage.rake +11 -0
  51. data/rakelib/gem.rake +54 -0
  52. data/rakelib/parser_specs/formatter.rb +31 -0
  53. data/rakelib/parser_specs/parser_specs.rb.txt.erb +35 -0
  54. data/rakelib/parser_specs/rule.rb +95 -0
  55. data/rakelib/parser_specs/suite.rb +91 -0
  56. data/rakelib/parser_specs/test.rb +97 -0
  57. data/rakelib/parser_specs.rb +1 -0
  58. data/rakelib/rubocop.rake +18 -0
  59. data/rakelib/spec.rake +27 -0
  60. data/ruby-marc-spec.gemspec +42 -0
  61. data/spec/.rubocop.yml +46 -0
  62. data/spec/README.md +16 -0
  63. data/spec/data/b23161018-sru.xml +182 -0
  64. data/spec/data/sandburg.xml +82 -0
  65. data/spec/generated/char_indicator_spec.rb +174 -0
  66. data/spec/generated/char_spec.rb +113 -0
  67. data/spec/generated/comparison_string_spec.rb +74 -0
  68. data/spec/generated/field_tag_spec.rb +156 -0
  69. data/spec/generated/index_char_spec.rb +669 -0
  70. data/spec/generated/index_indicator_spec.rb +174 -0
  71. data/spec/generated/index_spec.rb +113 -0
  72. data/spec/generated/index_sub_spec_spec.rb +1087 -0
  73. data/spec/generated/indicators_spec.rb +75 -0
  74. data/spec/generated/position_or_range_spec.rb +110 -0
  75. data/spec/generated/sub_spec_spec.rb +208 -0
  76. data/spec/generated/sub_spec_sub_spec_spec.rb +1829 -0
  77. data/spec/generated/subfield_char_spec.rb +405 -0
  78. data/spec/generated/subfield_range_range_spec.rb +48 -0
  79. data/spec/generated/subfield_range_spec.rb +87 -0
  80. data/spec/generated/subfield_range_sub_spec_spec.rb +214 -0
  81. data/spec/generated/subfield_tag_range_spec.rb +477 -0
  82. data/spec/generated/subfield_tag_sub_spec_spec.rb +3216 -0
  83. data/spec/generated/subfield_tag_tag_spec.rb +5592 -0
  84. data/spec/marc/spec/parsing/closed_int_range_spec.rb +49 -0
  85. data/spec/marc/spec/parsing/closed_lc_alpha_range_spec.rb +49 -0
  86. data/spec/marc/spec/parsing/parser_spec.rb +545 -0
  87. data/spec/marc/spec/queries/al_num_range_spec.rb +114 -0
  88. data/spec/marc/spec/queries/character_spec_spec.rb +28 -0
  89. data/spec/marc/spec/queries/comparison_string_spec.rb +28 -0
  90. data/spec/marc/spec/queries/indicator_value_spec.rb +28 -0
  91. data/spec/marc/spec/queries/query_spec.rb +200 -0
  92. data/spec/marc/spec/queries/subfield_spec.rb +92 -0
  93. data/spec/marc/spec/queries/subfield_value_spec.rb +31 -0
  94. data/spec/marc/spec/queries/tag_spec.rb +144 -0
  95. data/spec/marc/spec/queries/transform_spec.rb +459 -0
  96. data/spec/marc_spec_spec.rb +247 -0
  97. data/spec/scratch_spec.rb +112 -0
  98. data/spec/spec_helper.rb +23 -0
  99. metadata +341 -0
data/.rubocop.yml ADDED
@@ -0,0 +1,269 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+ - Include
5
+
6
+ # Allow one line around block body (Layout/EmptyLines will still disallow two or more)
7
+ Layout/EmptyLinesAroundBlockBody:
8
+ Enabled: false
9
+
10
+ # Allow one line around class body (Layout/EmptyLines will still disallow two or more)
11
+ Layout/EmptyLinesAroundClassBody:
12
+ Enabled: false
13
+
14
+ # Allow one line around module body (Layout/EmptyLines will still disallow two or more)
15
+ Layout/EmptyLinesAroundModuleBody:
16
+ Enabled: false
17
+
18
+ # Reasonable line-length check; it's too easy for the cure to be worse than the disease
19
+ Layout/LineLength:
20
+ Max: 150
21
+
22
+ # Make indents consistent regardless of the lengths of variables and method names and whatnot
23
+ Layout/MultilineMethodCallIndentation:
24
+ EnforcedStyle: indented
25
+
26
+ # Produces monsters
27
+ Layout/MultilineOperationIndentation:
28
+ Enabled: false
29
+
30
+ # We meant to do that
31
+ Naming/MemoizedInstanceVariableName:
32
+ Enabled: False
33
+
34
+ # It works in context, trust us
35
+ Naming/MethodParameterName:
36
+ Enabled: False
37
+
38
+ # Confusing and weird
39
+ Naming/VariableNumber:
40
+ Enabled: False
41
+
42
+ # Do what's readable in the context you're in
43
+ Style/AccessModifierDeclarations:
44
+ Enabled: false
45
+
46
+ # 👎 to cultural imperialism
47
+ Style/AsciiComments:
48
+ Enabled: false
49
+
50
+ # Seriously?
51
+ Style/CommentedKeyword:
52
+ Enabled: False
53
+
54
+ # Disable problematic module documentation check (see https://github.com/bbatsov/rubocop/issues/947)
55
+ Style/Documentation:
56
+ Enabled: false
57
+
58
+ # Adding more line noise to format strings will not improve them
59
+ Style/FormatStringToken:
60
+ Enabled: false
61
+
62
+ # Putting '# frozen_string_literal: true' everywhere does not make the world a better place
63
+ Style/FrozenStringLiteralComment:
64
+ Enabled: false
65
+
66
+ # Requiring the lambda() method just makes wordy calls wordier
67
+ Style/Lambda:
68
+ EnforcedStyle: literal
69
+
70
+ # `foo.positive?` is cute, but it's not actually more readable than `foo > 0`
71
+ Style/NumericPredicate:
72
+ Enabled: false
73
+
74
+ # Unclear why it's a good idea to give parameters semantically meaningless names
75
+ Style/SingleLineBlockParams:
76
+ Enabled: false
77
+
78
+ # The semantics of `foo&.bar` are a lot less interchangeable with `foo && foo.bar` than RuboCop thinks
79
+ Style/SafeNavigation:
80
+ Enabled: false
81
+
82
+ # Short arrays are often more readable as plain arrays
83
+ Style/WordArray:
84
+ MinSize: 3
85
+
86
+ ############################################################
87
+ # Added in RuboCop 0.80
88
+
89
+ Style/HashEachMethods:
90
+ Enabled: true
91
+
92
+ Style/HashTransformKeys:
93
+ Enabled: true
94
+
95
+ Style/HashTransformValues:
96
+ Enabled: true
97
+
98
+ ############################################################
99
+ # Added in RuboCop 0.81
100
+
101
+ Lint/StructNewOverride:
102
+ Enabled: true
103
+
104
+ Lint/RaiseException:
105
+ Enabled: true
106
+
107
+ ############################################################
108
+ # Added in RuboCop 0.82
109
+
110
+ Layout/SpaceAroundMethodCallOperator:
111
+ Enabled: true
112
+
113
+ Style/ExponentialNotation:
114
+ Enabled: false
115
+
116
+ ############################################################
117
+ # Added in RuboCop 0.83
118
+
119
+ Layout/EmptyLinesAroundAttributeAccessor:
120
+ Enabled: true
121
+
122
+ Style/SlicingWithRange:
123
+ Enabled: true
124
+
125
+ ############################################################
126
+ # Added in RuboCop 0.84
127
+
128
+ Lint/DeprecatedOpenSSLConstant:
129
+ Enabled: true
130
+
131
+ ############################################################
132
+ # Added in RuboCop 0.85
133
+
134
+ Lint/MixedRegexpCaptureTypes:
135
+ Enabled: true
136
+
137
+ Style/RedundantRegexpEscape:
138
+ Enabled: true
139
+
140
+ Style/RedundantRegexpCharacterClass:
141
+ Enabled: true
142
+
143
+ ############################################################
144
+ # Added in Rubocop 0.86
145
+
146
+ Style/RedundantFetchBlock:
147
+ Enabled: true
148
+
149
+ ############################################################
150
+ # Added in Rubocop 0.87
151
+
152
+ # Sometimes we separate things for a reason
153
+ Style/AccessorGrouping:
154
+ Enabled: false
155
+
156
+ Style/BisectedAttrAccessor:
157
+ Enabled: true
158
+
159
+ Style/RedundantAssignment:
160
+ Enabled: true
161
+
162
+ ############################################################
163
+ # Added in Rubocop 0.88
164
+
165
+ Lint/DuplicateElsifCondition:
166
+ Enabled: true
167
+
168
+ # Indiscriminately calling `to_ary` and `to_a` isn't always a good idea
169
+ Style/ArrayCoercion:
170
+ Enabled: false
171
+
172
+ Style/CaseLikeIf:
173
+ Enabled: true
174
+
175
+ Style/HashAsLastArrayItem:
176
+ Enabled: true
177
+
178
+ Style/HashLikeCase:
179
+ Enabled: true
180
+
181
+ Style/RedundantFileExtensionInRequire:
182
+ Enabled: true
183
+
184
+ ############################################################
185
+ # Added in Rubocop 0.89
186
+
187
+ Lint/BinaryOperatorWithIdenticalOperands:
188
+ Enabled: true
189
+
190
+ Lint/DuplicateRescueException:
191
+ Enabled: true
192
+
193
+ Lint/EmptyConditionalBody:
194
+ Enabled: true
195
+
196
+ Lint/FloatComparison:
197
+ Enabled: true
198
+
199
+ Lint/MissingSuper:
200
+ Enabled: true
201
+
202
+ Lint/OutOfRangeRegexpRef:
203
+ Enabled: true
204
+
205
+ Lint/SelfAssignment:
206
+ Enabled: true
207
+
208
+ Lint/TopLevelReturnWithArgument:
209
+ Enabled: true
210
+
211
+ Lint/UnreachableLoop:
212
+ Enabled: true
213
+
214
+ Style/ExplicitBlockArgument:
215
+ Enabled: true
216
+
217
+ Style/GlobalStdStream:
218
+ Enabled: true
219
+
220
+ Style/OptionalBooleanParameter:
221
+ Enabled: true
222
+
223
+ Style/SingleArgumentDig:
224
+ Enabled: true
225
+
226
+ Style/SoleNestedConditional:
227
+ Enabled: true
228
+
229
+ Style/StringConcatenation:
230
+ Enabled: true
231
+
232
+ ############################################################
233
+ # Added in Rubocop 0.90
234
+
235
+ Lint/DuplicateRequire:
236
+ Enabled: true
237
+
238
+ Lint/EmptyFile:
239
+ Enabled: true
240
+
241
+ Lint/TrailingCommaInAttributeDeclaration:
242
+ Enabled: true
243
+
244
+ Lint/UselessMethodDefinition:
245
+ Enabled: true
246
+
247
+ Style/CombinableLoops:
248
+ Enabled: true
249
+
250
+ Style/KeywordParametersOrder:
251
+ Enabled: true
252
+
253
+ Style/RedundantSelfAssignment:
254
+ Enabled: true
255
+
256
+ ############################################################
257
+ # Added in Rubocop 0.91
258
+
259
+ Layout/BeginEndAlignment:
260
+ Enabled: true
261
+
262
+ Lint/ConstantDefinitionInBlock:
263
+ Enabled: true
264
+
265
+ Lint/IdentityComparison:
266
+ Enabled: true
267
+
268
+ Lint/UselessTimes:
269
+ Enabled: true
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.7
data/.simplecov ADDED
@@ -0,0 +1,8 @@
1
+ require 'simplecov-rcov'
2
+
3
+ SimpleCov.start 'rails' do
4
+ add_filter 'module_info.rb'
5
+ coverage_dir 'artifacts'
6
+ formatter SimpleCov::Formatter::RcovFormatter
7
+ minimum_coverage 100
8
+ end
data/CHANGES.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.1.0 (2021-03-15)
2
+
3
+ - Initial release.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # TODO: Remove once 1.1.2 or later is released
4
+ gem 'marc', '~> 1.1', git: 'https://github.com/ruby-marc/ruby-marc.git', ref: '335a02065136440c8fb57c8f18e99dcd6e23791a'
5
+
6
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # The MIT License (MIT)
2
+
3
+ Copyright © 2021 The Regents of the University of California
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a
6
+ copy of this software and associated documentation files (the “Software”),
7
+ to deal in the Software without restriction, including without limitation
8
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
9
+ and/or sell copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+ DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,172 @@
1
+ [![Build Status](https://github.com/BerkeleyLibrary/marc-spec/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/BerkeleyLibrary/marc-spec/actions/workflows/build.yml)
2
+ [![Gem Version](https://img.shields.io/gem/v/ruby-marc-spec.svg)](https://github.com/ruby-marc-spec/releases)
3
+
4
+ # MARC::Spec
5
+
6
+ A implementation of the [MARCspec](http://marcspec.github.io/MARCspec/marc-spec.html)
7
+ query language for Ruby and [ruby-marc](https://github.com/ruby-marc/ruby-marc).
8
+
9
+ ## Usage
10
+
11
+ Add `ruby-marc-spec` to your Gemfile or gemspec, or just install it:
12
+
13
+ - Gemfile:
14
+
15
+ ```ruby
16
+ gem 'ruby-marc-spec'
17
+ ```
18
+
19
+ - gemspec
20
+
21
+ ```ruby
22
+ spec.add_dependency 'ruby-marc-spec'
23
+ ```
24
+
25
+ - from the command line:
26
+
27
+ ```sh
28
+ gem install ruby-marc-spec
29
+ ```
30
+
31
+ Then, in your code:
32
+
33
+ ```ruby
34
+ require 'marc/spec'
35
+ ```
36
+
37
+ The entry point for `MARC::Spec` is the `MARC::Spec#find` method, which takes a string
38
+ MARCspec query and a `MARC::Record` object.
39
+
40
+ ```ruby
41
+ MARC::Spec.find('245$a', marc_record)
42
+ # => [#<MARC::Subfield:0x00007ffa1686e3f0 @code="a", @value="Arithmetic /">]
43
+ ```
44
+
45
+ Note that for simplicity's sake `MARC::Spec#find` always returns an array, even for
46
+ queries that can only return a single-element result, e.g.
47
+
48
+ ```ruby
49
+ MARC::Spec.find('LDR', marc_record)
50
+ # => ["01142cam 2200301 a 4500"]
51
+ ```
52
+
53
+ ## Examples
54
+
55
+ All examples below are based on the record [`sandburg.xml`](spec/data/sandburg.xml)
56
+ from the Library of Congress [MARCXML](https://www.loc.gov/standards/marcxml/) documentation.
57
+
58
+ ```ruby
59
+ marc_record = MARC::XMLReader.new('sandburg.xml').first
60
+ ```
61
+
62
+ ### Retrieving the leader
63
+
64
+ ```ruby
65
+ MARC::Spec.find('LDR', marc_record)
66
+ # => ["01142cam 2200301 a 4500"]
67
+ ```
68
+
69
+ ### Retrieving control fields
70
+
71
+ Find all fields whose tag begins with `00`:
72
+
73
+ ```ruby
74
+ MARC::Spec.find('00.', marc_record)
75
+ # => [
76
+ # #<MARC::ControlField:0x00007ff9f706ac40 @tag="001", @value=" 92005291 ">,
77
+ # #<MARC::ControlField:0x00007ff9f70686c0 @tag="003", @value="DLC">,
78
+ # #<MARC::ControlField:0x00007ff9f7062450 @tag="005", @value="19930521155141.9">,
79
+ # #<MARC::ControlField:0x00007ff9f70600d8 @tag="008", @value="920219s1993 caua j 000 0 eng ">
80
+ # ]
81
+ ```
82
+
83
+ ### Retrieving substrings of a control field value:
84
+
85
+ Find the first six characters (characters 0 through 6) of the `008` field:
86
+
87
+ ```ruby
88
+ MARC::Spec.find('008/0-5', marc_record)
89
+ # => ["920219"]
90
+ ```
91
+
92
+ ### Retrieving data fields
93
+
94
+ Find the first two `650` fields (fields 0 and 1):
95
+
96
+ ```ruby
97
+ MARC::Spec.find('650[0-1]', marc_record)
98
+ # => [#<MARC::DataField:0x00007ffa1799a5c0
99
+ # @indicator1=" ",
100
+ # @indicator2="0",
101
+ # @subfields=[#<MARC::Subfield:0x00007ffa17999878 @code="a", @value="Arithmetic">, #<MARC::Subfield:0x00007ffa179984a0 @code="x", @value="Juvenile poetry.">],
102
+ # @tag="650">,
103
+ # #<MARC::DataField:0x00007ffa17992618
104
+ # @indicator1=" ",
105
+ # @indicator2="0",
106
+ # @subfields=[#<MARC::Subfield:0x00007ffa179918d0 @code="a", @value="Children's poetry, American.">],
107
+ # @tag="650">]
108
+ ```
109
+
110
+ ### Retrieving subfields
111
+
112
+ Find subfield `a` of all 650 fields:
113
+
114
+ ```ruby
115
+ MARC::Spec.find('650$a', marc_record)
116
+ # =>
117
+ # [#<MARC::Subfield:0x00007ffa17999878 @code="a", @value="Arithmetic">,
118
+ # #<MARC::Subfield:0x00007ffa179918d0 @code="a", @value="Children's poetry, American.">,
119
+ # #<MARC::Subfield:0x00007ffa1798acb0 @code="a", @value="Arithmetic">,
120
+ # #<MARC::Subfield:0x00007ffa17982cb8 @code="a", @value="American poetry.">,
121
+ # #<MARC::Subfield:0x00007ffa17980120 @code="a", @value="Visual perception.">]
122
+ ```
123
+
124
+ ### Retrieving subfield values
125
+
126
+ Find the first six characters (characters 0 through 5) of subfield `a`
127
+ of the fifth (zero-indexed) `650` field:
128
+
129
+ ```ruby
130
+ MARC::Spec.find('650[4]$a/0-5', marc_record)
131
+ # => ["Visual"]
132
+ ```
133
+
134
+ ### Limiting results based on conditions:
135
+
136
+ Find all `650` fields having a value of `0` for the second indicator:
137
+
138
+ ```ruby
139
+ MARC::Spec.find('650{^2=\0}', marc_record)
140
+ # =>
141
+ # [#<MARC::DataField:0x00007ffa1799a5c0
142
+ # @indicator1=" ",
143
+ # @indicator2="0",
144
+ # @subfields=[#<MARC::Subfield:0x00007ffa17999878 @code="a", @value="Arithmetic">, #<MARC::Subfield:0x00007ffa179984a0 @code="x", @value="Juvenile poetry.">],
145
+ # @tag="650">,
146
+ # #<MARC::DataField:0x00007ffa17992618
147
+ # @indicator1=" ",
148
+ # @indicator2="0",
149
+ # @subfields=[#<MARC::Subfield:0x00007ffa179918d0 @code="a", @value="Children's poetry, American.">],
150
+ # @tag="650">]
151
+ ```
152
+
153
+ Find subfield `a` of each `650` field that also has a subfield `x`:
154
+
155
+ ```ruby
156
+ MARC::Spec.find('650$a{$x}', marc_record)
157
+ # => [#<MARC::Subfield:0x00007ffa17999878 @code="a", @value="Arithmetic">, #<MARC::Subfield:0x00007ffa1798acb0 @code="a", @value="Arithmetic">]
158
+ ```
159
+
160
+ Note that this `650$a{$x}` could also be written `650$a{?$x}`; the `?` ("exists") operator
161
+ is implicit if no other operator is specified.
162
+
163
+ Find the first seven characters of `260$b`, but only if the corresponding `$a` contains
164
+ the string `San Diego` and there is at least one `050$b` containing as a substring characters
165
+ 7 through 10 of the `008` field:
166
+
167
+ ```ruby
168
+ MARC::Spec.find('260$b/0-7{$a~\San\sDiego}{050$b~008/7-10}', marc_record)
169
+ # => ["Harcourt"]
170
+ ```
171
+
172
+ For further examples, see the MARCSpec [documentation](http://marcspec.github.io/MARCspec/marc-spec.html#marcspec-explained).
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('Gemfile', __dir__)
2
+ require 'bundler/setup' # Set up gems listed in the Gemfile.
3
+
4
+ # ------------------------------------------------------------
5
+ # Application code
6
+
7
+ File.expand_path('lib', __dir__).tap do |lib|
8
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
9
+ end
10
+
11
+ # ------------------------------------------------------------
12
+ # CI
13
+
14
+ ENV['RAILS_ENV'] = 'test' if ENV['CI']
15
+
16
+ # ------------------------------------------------------------
17
+ # Custom tasks
18
+
19
+ desc 'Run tests, check test coverage, check code style'
20
+ task default: %i[coverage rubocop bundle:audit gem]
data/lib/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_from: ../.rubocop.yml
2
+
3
+ Style/ParallelAssignment:
4
+ Enabled: false
5
+
@@ -0,0 +1,14 @@
1
+ module MARC
2
+ module Spec
3
+ class ModuleInfo
4
+ NAME = 'ruby-marc-spec'.freeze
5
+ AUTHOR = 'David Moles'.freeze
6
+ AUTHOR_EMAIL = 'dmoles@berkeley.edu'.freeze
7
+ SUMMARY = 'MARCspec for Ruby'.freeze
8
+ DESCRIPTION = 'An implementation of the MARCspec query language for Ruby and ruby-marc'.freeze
9
+ LICENSE = 'MIT'.freeze
10
+ VERSION = '0.1.0'.freeze
11
+ HOMEPAGE = 'https://github.com/BerkeleyLibrary/marc-spec'.freeze
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ require 'parslet'
2
+
3
+ module MARC
4
+ module Spec
5
+ module Parsing
6
+ class ClosedIntRange < Parslet::Atoms::Base
7
+ SIMPLE_CLASS_NAME = ClosedIntRange.name.split('::').last
8
+
9
+ INT_RANGE_RE = /^(0|[1-9][0-9]*)-(0|[1-9][0-9]*)/.freeze
10
+
11
+ def try(source, context, _consume_all)
12
+ source_str = source.instance_variable_get(:@str) # TODO: something less hacky
13
+ return context.err(self, source, 'Not a non-negative integer range') unless (range_str = source_str.check(INT_RANGE_RE))
14
+
15
+ s, e = range_str.match(INT_RANGE_RE)[1, 2]
16
+ return context.err(self, source, "#{s} !<= #{e}") unless s.to_i <= e.to_i
17
+
18
+ sv, _, ev = [s.size, 1, e.size].map { |l| source.consume(l) } # discard hyphen
19
+ succ(from: sv, to: ev)
20
+ end
21
+
22
+ def to_s_inner(_prec)
23
+ SIMPLE_CLASS_NAME
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'parslet'
2
+
3
+ module MARC
4
+ module Spec
5
+ module Parsing
6
+ class ClosedLcAlphaRange < Parslet::Atoms::Base
7
+ SIMPLE_CLASS_NAME = ClosedLcAlphaRange.name.split('::').last
8
+
9
+ LCALPHA_RANGE_RE = /^([a-z])-([a-z])/.freeze
10
+
11
+ def try(source, context, _consume_all)
12
+ source_str = source.instance_variable_get(:@str) # TODO: something less hacky
13
+ return context.err(self, source, 'Not a lower-case alphabetic range') unless (range_str = source_str.check(LCALPHA_RANGE_RE))
14
+
15
+ s, e = range_str.match(LCALPHA_RANGE_RE)[1, 2]
16
+ return context.err(self, source, "#{s} !<= #{e}") unless s < e
17
+
18
+ sv, _, ev = [s.size, 1, e.size].map { |l| source.consume(l) } # discard hyphen
19
+ succ(from: sv, to: ev)
20
+ end
21
+
22
+ def to_s_inner(_prec)
23
+ SIMPLE_CLASS_NAME
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end