ruby-marc-spec 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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