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
@@ -0,0 +1,80 @@
1
+ require 'marc/spec/queries/al_num_range'
2
+ require 'marc/spec/queries/position'
3
+ require 'marc/spec/queries/comparison_string'
4
+ require 'marc/spec/queries/condition'
5
+
6
+ require 'marc/spec/queries/character_spec'
7
+ require 'marc/spec/queries/indicator_value'
8
+ require 'marc/spec/queries/operator'
9
+ require 'marc/spec/queries/query'
10
+ require 'marc/spec/queries/subfield'
11
+ require 'marc/spec/queries/subfield_value'
12
+ require 'marc/spec/queries/tag'
13
+
14
+ module MARC
15
+ module Spec
16
+ module Queries
17
+ module DSL
18
+
19
+ def all_c(*c)
20
+ Condition.all_of(*c)
21
+ end
22
+
23
+ def any_c(*c)
24
+ Condition.any_of(*c)
25
+ end
26
+
27
+ def vfv(sf)
28
+ sf
29
+ end
30
+
31
+ def rng(from, to = nil)
32
+ AlNumRange.new(from, to)
33
+ end
34
+
35
+ def q(t = nil, s: nil, c: nil, sq: [])
36
+ Query.new(tag: t, selector: s, condition: c, subqueries: sq)
37
+ end
38
+
39
+ def pos(p)
40
+ Position.new(p)
41
+ end
42
+
43
+ def cstr(s)
44
+ ComparisonString.new(s)
45
+ end
46
+
47
+ def c(*args)
48
+ case args.size
49
+ when 2
50
+ Condition.new(args[0], right: args[1])
51
+ when 3
52
+ Condition.new(args[1], left: args[0], right: args[2])
53
+ end
54
+ end
55
+
56
+ def cspec(chars)
57
+ # noinspection RubyArgCount
58
+ CharacterSpec.new(chars)
59
+ end
60
+
61
+ def indv(ind)
62
+ IndicatorValue.new(ind)
63
+ end
64
+
65
+ def sf(code, index = nil)
66
+ Subfield.new(code, index: index)
67
+ end
68
+
69
+ def sfv(sf, cspc = nil)
70
+ cspc = cspec(cspc) if cspc.is_a?(PositionOrRange)
71
+ SubfieldValue.new(sf, cspc)
72
+ end
73
+
74
+ def tag(t, index = nil)
75
+ Tag.new(t, index)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,77 @@
1
+ require 'marc/spec/queries/selector'
2
+
3
+ module MARC
4
+ module Spec
5
+ module Queries
6
+ class IndicatorValue
7
+ include Selector
8
+
9
+ # ------------------------------------------------------------
10
+ # Constants
11
+
12
+ VALID_INDICATORS = [1, 2].freeze
13
+
14
+ # ------------------------------------------------------------
15
+ # Attributes
16
+
17
+ attr_reader :tag, :ind
18
+
19
+ # ------------------------------------------------------------
20
+ # Initializer
21
+
22
+ def initialize(ind)
23
+ @ind = valid_indicator(ind)
24
+ end
25
+
26
+ # ------------------------------------------------------------
27
+ # Object overrides
28
+
29
+ def to_s
30
+ "^#{ind}"
31
+ end
32
+
33
+ # ------------------------------
34
+ # Applicable
35
+
36
+ def can_apply?(marc_obj)
37
+ %i[indicator1 indicator2].all? { |m| marc_obj.respond_to?(m) }
38
+ end
39
+
40
+ # ------------------------------------------------------------
41
+ # Protected methods
42
+
43
+ protected
44
+
45
+ # ------------------------------
46
+ # Applicable
47
+
48
+ def do_apply(marc_obj)
49
+ ind_val = ind_val_for(marc_obj)
50
+ ind_val ? [ind_val] : []
51
+ end
52
+
53
+ def equality_attrs
54
+ %i[ind]
55
+ end
56
+
57
+ private
58
+
59
+ def ind_val_for(data_field)
60
+ case ind
61
+ when 1
62
+ data_field.indicator1
63
+ when 2
64
+ data_field.indicator2
65
+ end
66
+ end
67
+
68
+ def valid_indicator(ind_val)
69
+ ind = int_or_nil(ind_val)
70
+ return ind if VALID_INDICATORS.include?(ind)
71
+
72
+ raise ArgumentError, "Not a valid indicator: #{ind_val.inspect}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,129 @@
1
+ require 'typesafe_enum'
2
+
3
+ module MARC
4
+ module Spec
5
+ module Queries
6
+ # NOTE: && and || are not defined in the MARCspec standard but are
7
+ # implementation details of repeated (AND) and chained (OR) subspecs
8
+ # -- see https://marcspec.github.io/MARCspec/marc-spec.html#general
9
+ class Operator < TypesafeEnum::Base
10
+
11
+ # ------------------------------------------------------------
12
+ # Enum instances
13
+
14
+ new(:EQ, '=') do
15
+ def apply(lr, rr)
16
+ return false if rr.nil?
17
+ return rarr_eq?(lr, rr) if rr.is_a?(Array)
18
+ return larr_eq?(lr, rr) if lr.is_a?(Array)
19
+
20
+ lr == rr
21
+ end
22
+
23
+ def larr_eq?(larr, rr)
24
+ larr.any? { |l| apply(l, rr) }
25
+ end
26
+
27
+ def rarr_eq?(lr, rarr)
28
+ rarr.any? { |r| apply(lr, r) }
29
+ end
30
+ end
31
+
32
+ new(:NEQ, '!=') do
33
+ def apply(lr, rr)
34
+ !Operator::EQ.apply(lr, rr)
35
+ end
36
+ end
37
+
38
+ new(:INCL, '~') do
39
+ def apply(lr, rr)
40
+ return false if [lr, rr].any?(&:nil?) # TODO: is this right?
41
+ return rarr_incl?(lr, rr) if rr.is_a?(Array)
42
+ return larr_incl?(lr, rr) if lr.is_a?(Array)
43
+
44
+ lr.include?(rr)
45
+ end
46
+
47
+ def larr_incl?(larr, rr)
48
+ larr.any? { |l| apply(l, rr) }
49
+ end
50
+
51
+ def rarr_incl?(lr, rarr)
52
+ rarr.any? { |r| apply(lr, r) }
53
+ end
54
+ end
55
+
56
+ new(:NINCL, '!~') do
57
+ def apply(lr, rr)
58
+ !Operator::INCL.apply(lr, rr)
59
+ end
60
+ end
61
+
62
+ new(:NEXIST, '!') do
63
+ def apply(right)
64
+ right.is_a?(Array) ? right.empty? : right.nil?
65
+ end
66
+ end
67
+
68
+ new(:EXIST, '?') do
69
+ def apply(right)
70
+ !Operator::NEXIST.apply(right)
71
+ end
72
+ end
73
+
74
+ new(:AND, '&&') do
75
+ def apply(left, right)
76
+ left && right
77
+ end
78
+ end
79
+
80
+ new(:OR, '||') do
81
+ def apply(left, right)
82
+ left || right
83
+ end
84
+ end
85
+
86
+ # ------------------------------------------------------------
87
+ # Class methods
88
+
89
+ class << self
90
+ def from_str(op_str)
91
+ Operator.find_by_value(op_str.to_s).tap do |op|
92
+ raise ArgumentError, "No such operator: #{op_str}" unless op
93
+ end
94
+ end
95
+ end
96
+
97
+ # ------------------------------------------------------------
98
+ # Operator
99
+
100
+ def binary?
101
+ ![Operator::EXIST, Operator::NEXIST].include?(self)
102
+ end
103
+
104
+ def logical?
105
+ [Operator::AND, Operator::OR].include?(self)
106
+ end
107
+
108
+ # ------------------------------------------------------------
109
+ # Object overrides
110
+
111
+ def to_s
112
+ op_str
113
+ end
114
+
115
+ # @return [String]
116
+ def op_str
117
+ # noinspection RubyMismatchedReturnType
118
+ value
119
+ end
120
+
121
+ def to_expression(left, right)
122
+ return ["(#{left})", self, "(#{right})"].join if logical?
123
+
124
+ [left, self, right].join
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,63 @@
1
+ module MARC
2
+ module Spec
3
+ module Queries
4
+ # Supermodule of partial query
5
+ module Part
6
+
7
+ # ------------------------------------------------------------
8
+ # Object overrides
9
+
10
+ def inspect
11
+ "#{class_name(self)}<#{to_s_inspect}>"
12
+ end
13
+
14
+ def eql?(other)
15
+ return false unless other.class == self.class
16
+
17
+ equality_attrs.all? do |attr|
18
+ send(attr) == other.send(attr)
19
+ end
20
+ end
21
+
22
+ alias :== eql?
23
+
24
+ def hash
25
+ @hash_val ||= begin
26
+ equality_vals = equality_attrs.map { |attr| send(attr) }
27
+ equality_vals.inject(31 + self.class.hash) { |r, v| 31 * r + v.hash }
28
+ end
29
+ end
30
+
31
+ # ------------------------------------------------------------
32
+ # Protected methods
33
+
34
+ protected
35
+
36
+ def to_s_inspect
37
+ to_s
38
+ end
39
+
40
+ def ensure_type(v, type, allow_nil: false)
41
+ return if allow_nil && v.nil?
42
+ return v if v.is_a?(type)
43
+
44
+ raise ArgumentError, "Not a #{class_name(type)}: #{v.inspect}"
45
+ end
46
+
47
+ def int_or_nil(v)
48
+ return nil if v.nil? || v == '#'
49
+
50
+ Integer(v)
51
+ end
52
+
53
+ private
54
+
55
+ def class_name(t)
56
+ return class_name(t.class) unless t.is_a?(Class) || t.is_a?(Module)
57
+
58
+ t.name.sub(/^.*::/, '')
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,59 @@
1
+ require 'marc/spec/queries/position_or_range'
2
+
3
+ module MARC
4
+ module Spec
5
+ module Queries
6
+ class Position
7
+ include PositionOrRange
8
+
9
+ # ------------------------------------------------------------
10
+ # Attributes
11
+
12
+ attr_reader :position
13
+
14
+ def initialize(position)
15
+ @position = int_or_nil(position)
16
+ end
17
+
18
+ # ------------------------------------------------------------
19
+ # Instance methods
20
+
21
+ # @overload select_from(seq)
22
+ # Selects the value at the specified position and returns it as a
23
+ # single-element array, for compatibility with AlNumRange
24
+ # @param seq [Array] the input array
25
+ # @return [Array] a single-element array containing the result, or
26
+ # an empty array if the index is out of bounds
27
+ # @overload select_from(seq)
28
+ # Selects the character at the specified position
29
+ # @param seq [String] the input array
30
+ # @return [String, nil] a single-element array, or nil if the index
31
+ # is out of bounds
32
+ def select_from(seq)
33
+ # we use raw_result[-1] instead of raw_result.last b/c seq might be a string
34
+ raw_result = seq[position.nil? ? -1 : position]
35
+ seq.is_a?(String) ? wrap_string_result(raw_result) : wrap_array_result(raw_result)
36
+ end
37
+
38
+ # ------------------------------------------------------------
39
+ # Object overrides
40
+
41
+ def to_s
42
+ (position || '#').to_s
43
+ end
44
+
45
+ # ------------------------------------------------------------
46
+ # Protected methods
47
+
48
+ protected
49
+
50
+ # ------------------------------
51
+ # Part
52
+
53
+ def equality_attrs
54
+ [:position]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,27 @@
1
+ require 'marc/spec/queries/applicable'
2
+
3
+ module MARC
4
+ module Spec
5
+ module Queries
6
+ # Marker interface for positions and ranges
7
+ # TODO: unify Position and AlNumRange?
8
+ module PositionOrRange
9
+ include Part
10
+
11
+ protected
12
+
13
+ def wrap_string_result(result)
14
+ result unless result.nil? || result.empty?
15
+ end
16
+
17
+ # NOTE: We can't use `Array()` because we don't want to indiscriminately call `to_ary` / `to_a`
18
+ def wrap_array_result(result)
19
+ return [] unless result
20
+ return result if result.is_a?(Array)
21
+
22
+ [result]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,94 @@
1
+ require 'marc/spec/queries/part'
2
+
3
+ module MARC
4
+ module Spec
5
+ module Queries
6
+ class Query
7
+ include Part
8
+
9
+ attr_reader :tag, :selector, :condition, :subqueries
10
+
11
+ # TODO: separate factory methods for possible cases? (see transform.rb)
12
+ # TODO: separate query (w/o subqueries) from wrapper w/subqueries?
13
+ def initialize(tag: nil, selector: nil, condition: nil, subqueries: [])
14
+ @tag = ensure_type(tag, Tag, allow_nil: true)
15
+ # TODO: do we need the Selector interface at all?
16
+ @selector = ensure_type(selector, Applicable, allow_nil: true)
17
+ @condition = ensure_type(condition, Condition, allow_nil: true)
18
+ @subqueries = subqueries.map { |sq| ensure_type(sq, Query) }
19
+ end
20
+
21
+ def to_s
22
+ StringIO.new.tap do |out|
23
+ out << tag if tag
24
+ out << selector if selector
25
+ out << "{#{condition}}" if condition
26
+ out << subqueries.join
27
+ end.string
28
+ end
29
+
30
+ # TODO: don't support nested subqueries
31
+ def execute(executor, context_fields, context_result = nil)
32
+ fields = tag ? executor.apply_tag(tag) : context_fields
33
+ return [] if fields.empty?
34
+
35
+ field_results = root_results(fields, executor, context_result)
36
+ return field_results if subqueries.empty?
37
+
38
+ fields.each_with_object([]) do |field, results|
39
+ subqueries.each do |subquery|
40
+ subquery_results = subquery.execute(executor, [field])
41
+ results.concat(subquery_results)
42
+ end
43
+ end
44
+ end
45
+
46
+ protected
47
+
48
+ def equality_attrs
49
+ %i[tag selector condition subqueries]
50
+ end
51
+
52
+ # rubocop:disable Metrics/AbcSize
53
+ def to_s_inspect
54
+ StringIO.new.tap do |out|
55
+ out << tag.inspect if tag
56
+ out << selector.inspect if selector
57
+ out << "{#{condition.inspect}}" if condition
58
+ out << subqueries.map(&:inspect).join
59
+ end.string
60
+ end
61
+ # rubocop:enable Metrics/AbcSize
62
+
63
+ private
64
+
65
+ def root_results(fields, executor, context_result)
66
+ field_results = results_for_fields(executor, fields)
67
+ # TODO: something less ridiculous
68
+ return field_results unless field_results.empty? && select_from_context?(context_result)
69
+
70
+ selector.apply(context_result)
71
+ end
72
+
73
+ def select_from_context?(context_result)
74
+ tag.nil? && context_result && selector.can_apply?(context_result)
75
+ end
76
+
77
+ def results_for_fields(executor, fields)
78
+ fields.each_with_object([]) do |field, results|
79
+ field_results = results_for_field(executor, field)
80
+ results.concat(field_results)
81
+ end
82
+ end
83
+
84
+ def results_for_field(executor, field)
85
+ results = executor.apply_selector(selector, field)
86
+ return results unless condition
87
+
88
+ results.select { |result| executor.condition_met?(condition, field, result) }
89
+ end
90
+
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,52 @@
1
+ require 'marc'
2
+ require 'marc/spec/queries/query'
3
+
4
+ module MARC
5
+ module Spec
6
+ module Queries
7
+ class QueryExecutor
8
+ include Part
9
+
10
+ attr_reader :marc_record, :root_query, :root_tag, :root_fields
11
+
12
+ def initialize(marc_record, root, cache = {})
13
+ @marc_record = ensure_type(marc_record, MARC::Record)
14
+ @root_query = as_query(root)
15
+ @cache = cache
16
+
17
+ @root_tag = root_query.tag || Tag.new('...')
18
+ @root_fields = apply_tag(root_tag)
19
+ end
20
+
21
+ def execute
22
+ root_query.execute(self, root_fields)
23
+ end
24
+
25
+ def apply_tag(tag)
26
+ cache[tag] ||= tag.apply(marc_record)
27
+ end
28
+
29
+ def apply_selector(selector, field)
30
+ return [field] unless selector
31
+
32
+ cache_key = [selector, field]
33
+ cache[cache_key] ||= selector.apply(field)
34
+ end
35
+
36
+ def condition_met?(condition, context_field, context_result)
37
+ cond_ctx = ConditionContext.new(context_field, context_result, self)
38
+ condition.met?(cond_ctx)
39
+ end
40
+
41
+ private
42
+
43
+ def as_query(root)
44
+ return root if root.is_a?(Query)
45
+ return Query.new(tag: root) if root.is_a?(Tag)
46
+ end
47
+
48
+ attr_reader :cache
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,12 @@
1
+ require 'marc/spec/queries/applicable'
2
+
3
+ module MARC
4
+ module Spec
5
+ module Queries
6
+ # Marker interface for query objects that select data from fields
7
+ module Selector
8
+ include Applicable
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,88 @@
1
+ require 'stringio'
2
+ require 'marc/spec/queries/selector'
3
+
4
+ module MARC
5
+ module Spec
6
+ module Queries
7
+ class Subfield
8
+ include Selector
9
+
10
+ # ------------------------------------------------------------
11
+ # Attributes
12
+
13
+ attr_reader :code, :index
14
+
15
+ # ------------------------------------------------------------
16
+ # Initializer
17
+
18
+ def initialize(code, index: nil)
19
+ @code = parse_code(code)
20
+ @index = ensure_type(index, PositionOrRange, allow_nil: true)
21
+ end
22
+
23
+ # ------------------------------------------------------------
24
+ # Object overrides
25
+
26
+ def to_s
27
+ StringIO.new.tap do |out|
28
+ out << '$'
29
+ out << code
30
+ out << "[#{index}]" if index
31
+ end.string
32
+ end
33
+
34
+ # ------------------------------
35
+ # Applicable
36
+
37
+ def can_apply?(marc_obj)
38
+ marc_obj.respond_to?(:subfields)
39
+ end
40
+
41
+ # ------------------------------------------------------------
42
+ # Protected methods
43
+
44
+ protected
45
+
46
+ # ------------------------------
47
+ # Applicable
48
+
49
+ def do_apply(data_field)
50
+ subfields = all_subfields(data_field)
51
+ raw_result = index ? index.select_from(subfields) : subfields
52
+ Array(raw_result)
53
+ end
54
+
55
+ # ------------------------------
56
+ # Predicate
57
+
58
+ def to_s_inspect
59
+ StringIO.new.tap do |out|
60
+ out << '$'
61
+ out << code.inspect
62
+ out << "[#{index.inspect}]" if index
63
+ end.string
64
+ end
65
+
66
+ def equality_attrs
67
+ %i[code index]
68
+ end
69
+
70
+ # ------------------------------------------------------------
71
+ # Private methods
72
+
73
+ private
74
+
75
+ def all_subfields(data_field)
76
+ data_field.subfields.select { |sf| code.include?(sf.code) }
77
+ end
78
+
79
+ def parse_code(code)
80
+ raise ArgumentError, 'Code cannot be nil' if code.nil?
81
+
82
+ code.is_a?(AlNumRange) ? code : code.to_s
83
+ end
84
+
85
+ end
86
+ end
87
+ end
88
+ end