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
@@ -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