ruby-marc-spec 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/build.yml +18 -0
- data/.gitignore +388 -0
- data/.gitmodules +3 -0
- data/.idea/codeStyles/codeStyleConfig.xml +5 -0
- data/.idea/go.imports.xml +6 -0
- data/.idea/inspectionProfiles/Project_Default.xml +23 -0
- data/.idea/marc_spec.iml +102 -0
- data/.idea/misc.xml +6 -0
- data/.idea/modules.xml +8 -0
- data/.idea/templateLanguages.xml +6 -0
- data/.idea/vcs.xml +7 -0
- data/.rubocop.yml +269 -0
- data/.ruby-version +1 -0
- data/.simplecov +8 -0
- data/CHANGES.md +3 -0
- data/Gemfile +6 -0
- data/LICENSE.md +21 -0
- data/README.md +172 -0
- data/Rakefile +20 -0
- data/lib/.rubocop.yml +5 -0
- data/lib/marc/spec/module_info.rb +14 -0
- data/lib/marc/spec/parsing/closed_int_range.rb +28 -0
- data/lib/marc/spec/parsing/closed_lc_alpha_range.rb +28 -0
- data/lib/marc/spec/parsing/parser.rb +213 -0
- data/lib/marc/spec/parsing.rb +1 -0
- data/lib/marc/spec/queries/al_num_range.rb +105 -0
- data/lib/marc/spec/queries/applicable.rb +18 -0
- data/lib/marc/spec/queries/character_spec.rb +81 -0
- data/lib/marc/spec/queries/comparison_string.rb +45 -0
- data/lib/marc/spec/queries/condition.rb +133 -0
- data/lib/marc/spec/queries/condition_context.rb +49 -0
- data/lib/marc/spec/queries/dsl.rb +80 -0
- data/lib/marc/spec/queries/indicator_value.rb +77 -0
- data/lib/marc/spec/queries/operator.rb +129 -0
- data/lib/marc/spec/queries/part.rb +63 -0
- data/lib/marc/spec/queries/position.rb +59 -0
- data/lib/marc/spec/queries/position_or_range.rb +27 -0
- data/lib/marc/spec/queries/query.rb +94 -0
- data/lib/marc/spec/queries/query_executor.rb +52 -0
- data/lib/marc/spec/queries/selector.rb +12 -0
- data/lib/marc/spec/queries/subfield.rb +88 -0
- data/lib/marc/spec/queries/subfield_value.rb +63 -0
- data/lib/marc/spec/queries/tag.rb +107 -0
- data/lib/marc/spec/queries/transform.rb +154 -0
- data/lib/marc/spec/queries.rb +1 -0
- data/lib/marc/spec.rb +32 -0
- data/rakelib/.rubocop.yml +19 -0
- data/rakelib/bundle.rake +8 -0
- data/rakelib/coverage.rake +11 -0
- data/rakelib/gem.rake +54 -0
- data/rakelib/parser_specs/formatter.rb +31 -0
- data/rakelib/parser_specs/parser_specs.rb.txt.erb +35 -0
- data/rakelib/parser_specs/rule.rb +95 -0
- data/rakelib/parser_specs/suite.rb +91 -0
- data/rakelib/parser_specs/test.rb +97 -0
- data/rakelib/parser_specs.rb +1 -0
- data/rakelib/rubocop.rake +18 -0
- data/rakelib/spec.rake +27 -0
- data/ruby-marc-spec.gemspec +42 -0
- data/spec/.rubocop.yml +46 -0
- data/spec/README.md +16 -0
- data/spec/data/b23161018-sru.xml +182 -0
- data/spec/data/sandburg.xml +82 -0
- data/spec/generated/char_indicator_spec.rb +174 -0
- data/spec/generated/char_spec.rb +113 -0
- data/spec/generated/comparison_string_spec.rb +74 -0
- data/spec/generated/field_tag_spec.rb +156 -0
- data/spec/generated/index_char_spec.rb +669 -0
- data/spec/generated/index_indicator_spec.rb +174 -0
- data/spec/generated/index_spec.rb +113 -0
- data/spec/generated/index_sub_spec_spec.rb +1087 -0
- data/spec/generated/indicators_spec.rb +75 -0
- data/spec/generated/position_or_range_spec.rb +110 -0
- data/spec/generated/sub_spec_spec.rb +208 -0
- data/spec/generated/sub_spec_sub_spec_spec.rb +1829 -0
- data/spec/generated/subfield_char_spec.rb +405 -0
- data/spec/generated/subfield_range_range_spec.rb +48 -0
- data/spec/generated/subfield_range_spec.rb +87 -0
- data/spec/generated/subfield_range_sub_spec_spec.rb +214 -0
- data/spec/generated/subfield_tag_range_spec.rb +477 -0
- data/spec/generated/subfield_tag_sub_spec_spec.rb +3216 -0
- data/spec/generated/subfield_tag_tag_spec.rb +5592 -0
- data/spec/marc/spec/parsing/closed_int_range_spec.rb +49 -0
- data/spec/marc/spec/parsing/closed_lc_alpha_range_spec.rb +49 -0
- data/spec/marc/spec/parsing/parser_spec.rb +545 -0
- data/spec/marc/spec/queries/al_num_range_spec.rb +114 -0
- data/spec/marc/spec/queries/character_spec_spec.rb +28 -0
- data/spec/marc/spec/queries/comparison_string_spec.rb +28 -0
- data/spec/marc/spec/queries/indicator_value_spec.rb +28 -0
- data/spec/marc/spec/queries/query_spec.rb +200 -0
- data/spec/marc/spec/queries/subfield_spec.rb +92 -0
- data/spec/marc/spec/queries/subfield_value_spec.rb +31 -0
- data/spec/marc/spec/queries/tag_spec.rb +144 -0
- data/spec/marc/spec/queries/transform_spec.rb +459 -0
- data/spec/marc_spec_spec.rb +247 -0
- data/spec/scratch_spec.rb +112 -0
- data/spec/spec_helper.rb +23 -0
- metadata +341 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'marc/spec/queries/selector'
|
3
|
+
require 'marc/spec/queries/subfield'
|
4
|
+
|
5
|
+
module MARC
|
6
|
+
module Spec
|
7
|
+
module Queries
|
8
|
+
class SubfieldValue
|
9
|
+
include Selector
|
10
|
+
|
11
|
+
# ------------------------------------------------------------
|
12
|
+
# Attributes
|
13
|
+
|
14
|
+
attr_reader :subfield, :character_spec
|
15
|
+
|
16
|
+
# ------------------------------------------------------------
|
17
|
+
# Initializer
|
18
|
+
|
19
|
+
def initialize(subfield, character_spec = nil)
|
20
|
+
@subfield = ensure_type(subfield, Subfield)
|
21
|
+
@character_spec = ensure_type(character_spec, CharacterSpec, allow_nil: true)
|
22
|
+
end
|
23
|
+
|
24
|
+
# ------------------------------------------------------------
|
25
|
+
# Object overrides
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
StringIO.new.tap do |out|
|
29
|
+
out << subfield
|
30
|
+
out << "/#{character_spec}" if character_spec
|
31
|
+
end.string
|
32
|
+
end
|
33
|
+
|
34
|
+
# ------------------------------------------------------------
|
35
|
+
# Protected
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
def can_apply?(marc_obj)
|
40
|
+
subfield.send(:can_apply?, marc_obj)
|
41
|
+
end
|
42
|
+
|
43
|
+
def do_apply(data_field)
|
44
|
+
subfields = subfield.apply(data_field)
|
45
|
+
return subfields.map(&:value) unless character_spec
|
46
|
+
|
47
|
+
subfields.flat_map { |sf| character_spec.apply(sf.value) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s_inspect
|
51
|
+
StringIO.new.tap do |out|
|
52
|
+
out << subfield.inspect
|
53
|
+
out << "/#{character_spec.inspect}" if character_spec
|
54
|
+
end.string
|
55
|
+
end
|
56
|
+
|
57
|
+
def equality_attrs
|
58
|
+
%i[subfield character_spec]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'marc'
|
2
|
+
require 'marc/spec/queries/applicable'
|
3
|
+
|
4
|
+
module MARC
|
5
|
+
module Spec
|
6
|
+
module Queries
|
7
|
+
class Tag
|
8
|
+
include Applicable
|
9
|
+
|
10
|
+
# ------------------------------------------------------------
|
11
|
+
# Constants
|
12
|
+
|
13
|
+
LDR = 'LDR'.freeze
|
14
|
+
|
15
|
+
# ------------------------------------------------------------
|
16
|
+
# Attributes
|
17
|
+
|
18
|
+
attr_reader :index, :tag_re, :tag_exact
|
19
|
+
|
20
|
+
# ------------------------------------------------------------
|
21
|
+
# Initializer
|
22
|
+
|
23
|
+
def initialize(tag, index = nil)
|
24
|
+
raise ArgumentError, 'Tag cannot be nil' unless tag
|
25
|
+
|
26
|
+
@tag_exact = tag.to_s unless (@tag_re = tag_re_from(tag))
|
27
|
+
@index = ensure_type(index, PositionOrRange, allow_nil: true)
|
28
|
+
end
|
29
|
+
|
30
|
+
# ------------------------------------------------------------
|
31
|
+
# Public methods
|
32
|
+
|
33
|
+
def leader?
|
34
|
+
tag_exact == LDR
|
35
|
+
end
|
36
|
+
|
37
|
+
# ------------------------------------------------------------
|
38
|
+
# Object overrides
|
39
|
+
|
40
|
+
def to_s
|
41
|
+
StringIO.new.tap do |out|
|
42
|
+
out << tag_str
|
43
|
+
out << "[#{index}]" if index
|
44
|
+
end.string
|
45
|
+
end
|
46
|
+
|
47
|
+
# ------------------------------------------------------------
|
48
|
+
# Protected methods
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
# ------------------------------
|
53
|
+
# Applicable
|
54
|
+
|
55
|
+
def can_apply?(marc_obj)
|
56
|
+
marc_obj.is_a?(MARC::Record)
|
57
|
+
end
|
58
|
+
|
59
|
+
def do_apply(marc_record)
|
60
|
+
return [marc_record.leader] if leader?
|
61
|
+
|
62
|
+
all_fields = all_fields(marc_record)
|
63
|
+
index ? index.select_from(all_fields) : all_fields
|
64
|
+
end
|
65
|
+
|
66
|
+
# ------------------------------
|
67
|
+
# Predicate
|
68
|
+
|
69
|
+
def to_s_inspect
|
70
|
+
StringIO.new.tap do |out|
|
71
|
+
out << (tag_re ? tag_re.inspect : tag_exact)
|
72
|
+
out << "[#{index.inspect}]" if index
|
73
|
+
end.string
|
74
|
+
end
|
75
|
+
|
76
|
+
def equality_attrs
|
77
|
+
%i[tag_str index]
|
78
|
+
end
|
79
|
+
|
80
|
+
# ------------------------------------------------------------
|
81
|
+
# Private methods
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def all_fields(marc_record)
|
86
|
+
return marc_record.fields(tag_exact) if tag_exact
|
87
|
+
|
88
|
+
[].tap do |ff|
|
89
|
+
ff << marc_record.leader if LDR =~ tag_re
|
90
|
+
marc_record.each { |field| ff << field if field.tag =~ tag_re }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def tag_re_from(tag)
|
95
|
+
return tag if tag.is_a?(Regexp)
|
96
|
+
|
97
|
+
tag_s = tag.to_s
|
98
|
+
Regexp.compile("^#{tag_s}$") if tag_s.include?('.')
|
99
|
+
end
|
100
|
+
|
101
|
+
def tag_str
|
102
|
+
@tag_str ||= tag_exact || tag_re.source.gsub(/^\^(.*)\$$/, '\\1')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
|
3
|
+
module MARC
|
4
|
+
module Spec
|
5
|
+
module Queries
|
6
|
+
class Transform < Parslet::Transform
|
7
|
+
|
8
|
+
# ----------------------------------------
|
9
|
+
# Misc. atoms
|
10
|
+
|
11
|
+
# { pos: }
|
12
|
+
rule(pos: simple(:pos)) { Position.new(pos) }
|
13
|
+
|
14
|
+
# { from:, to: }
|
15
|
+
rule(from: simple(:from), to: simple(:to)) { AlNumRange.new(from, to) }
|
16
|
+
|
17
|
+
# { comparison_string: }
|
18
|
+
rule(comparison_string: simple(:string)) { ComparisonString.new(string) }
|
19
|
+
|
20
|
+
# { character_spec: }
|
21
|
+
rule(character_spec: simple(:character_spec)) do
|
22
|
+
# noinspection RubyArgCount
|
23
|
+
CharacterSpec.new(character_spec)
|
24
|
+
end
|
25
|
+
|
26
|
+
# ----------------------------------------
|
27
|
+
# subTermSet
|
28
|
+
|
29
|
+
# { left:, operator:, right: }
|
30
|
+
rule(left: simple(:left), operator: simple(:operator), right: simple(:right)) do
|
31
|
+
Condition.new(operator, left: left, right: right)
|
32
|
+
end
|
33
|
+
|
34
|
+
# { operator:, right: }
|
35
|
+
rule(operator: simple(:operator), right: simple(:right)) do
|
36
|
+
Condition.new(operator, right: right)
|
37
|
+
end
|
38
|
+
|
39
|
+
# { right: }
|
40
|
+
rule(right: simple(:right)) do
|
41
|
+
Condition.new(right: right)
|
42
|
+
end
|
43
|
+
|
44
|
+
# { any_condition: }
|
45
|
+
rule(any_condition: sequence(:conditions)) do
|
46
|
+
Condition.any_of(*conditions)
|
47
|
+
end
|
48
|
+
|
49
|
+
# { all_conditions: }
|
50
|
+
rule(all_conditions: sequence(:conditions)) do
|
51
|
+
Condition.all_of(*conditions)
|
52
|
+
end
|
53
|
+
|
54
|
+
# ----------------------------------------
|
55
|
+
# fieldSpec
|
56
|
+
|
57
|
+
# { tag: }
|
58
|
+
rule(tag: simple(:tag)) do
|
59
|
+
Tag.new(tag)
|
60
|
+
end
|
61
|
+
|
62
|
+
# { tag:, index: }
|
63
|
+
rule(tag: simple(:tag), index: simple(:index)) do
|
64
|
+
Tag.new(tag, index)
|
65
|
+
end
|
66
|
+
|
67
|
+
# ----------------------------------------
|
68
|
+
# abrSubfieldSpec
|
69
|
+
|
70
|
+
# { code: }
|
71
|
+
rule(code: simple(:code)) do
|
72
|
+
Subfield.new(code)
|
73
|
+
end
|
74
|
+
|
75
|
+
# { code:, index: }
|
76
|
+
rule(code: simple(:code), index: simple(:index)) do
|
77
|
+
Subfield.new(code, index: index)
|
78
|
+
end
|
79
|
+
|
80
|
+
# { code:, sf_chars: }
|
81
|
+
rule(code: simple(:code), sf_chars: simple(:sf_chars)) do
|
82
|
+
SubfieldValue.new(Subfield.new(code), sf_chars)
|
83
|
+
end
|
84
|
+
|
85
|
+
# { code:, index:, sf_chars: }
|
86
|
+
rule(code: simple(:code), index: simple(:index), sf_chars: simple(:sf_chars)) do
|
87
|
+
SubfieldValue.new(Subfield.new(code, index: index), sf_chars)
|
88
|
+
end
|
89
|
+
|
90
|
+
# ----------------------------------------
|
91
|
+
# indicatorSpec
|
92
|
+
|
93
|
+
# { ind: }
|
94
|
+
rule(ind: simple(:ind)) do
|
95
|
+
IndicatorValue.new(ind)
|
96
|
+
end
|
97
|
+
|
98
|
+
# ----------------------------------------
|
99
|
+
# subSpec
|
100
|
+
|
101
|
+
# TODO: separate Subquery type?
|
102
|
+
|
103
|
+
# { selector: }
|
104
|
+
rule(selector: simple(:selector)) do
|
105
|
+
Query.new(selector: selector)
|
106
|
+
end
|
107
|
+
|
108
|
+
# { selector:, condition: }
|
109
|
+
rule(selector: simple(:selector), condition: simple(:condition)) do
|
110
|
+
Query.new(selector: selector, condition: condition)
|
111
|
+
end
|
112
|
+
|
113
|
+
# ----------------------------------------
|
114
|
+
# MARCSpec
|
115
|
+
|
116
|
+
# { tag:, selector: }
|
117
|
+
rule(tag: simple(:tag), selector: simple(:selector)) do
|
118
|
+
Query.new(tag: Tag.new(tag), selector: selector)
|
119
|
+
end
|
120
|
+
|
121
|
+
# { tag:, index:, selector: }
|
122
|
+
rule(tag: simple(:tag), index: simple(:index), selector: simple(:selector)) do
|
123
|
+
Query.new(tag: Tag.new(tag, index), selector: selector)
|
124
|
+
end
|
125
|
+
|
126
|
+
# { tag:, condition: }
|
127
|
+
rule(tag: simple(:tag), condition: simple(:condition)) do
|
128
|
+
Query.new(tag: Tag.new(tag), condition: condition)
|
129
|
+
end
|
130
|
+
|
131
|
+
# { tag:, index:, condition: }
|
132
|
+
rule(tag: simple(:tag), index: simple(:index), condition: simple(:condition)) do
|
133
|
+
Query.new(tag: Tag.new(tag, index), condition: condition)
|
134
|
+
end
|
135
|
+
|
136
|
+
# { tag:, subqueries: }
|
137
|
+
rule(tag: simple(:tag), subqueries: sequence(:subqueries)) do
|
138
|
+
Query.new(tag: Tag.new(tag), subqueries: subqueries)
|
139
|
+
end
|
140
|
+
|
141
|
+
# { tag:, selector:, condition: }
|
142
|
+
rule(tag: simple(:tag), selector: simple(:selector), condition: simple(:condition)) do
|
143
|
+
Query.new(tag: Tag.new(tag), selector: selector, condition: condition)
|
144
|
+
end
|
145
|
+
|
146
|
+
# { tag:, index:, selector:, condition: }
|
147
|
+
rule(tag: simple(:tag), index: simple(:index), selector: simple(:selector), condition: simple(:condition)) do
|
148
|
+
Query.new(tag: Tag.new(tag, index), selector: selector, condition: condition)
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir.glob(File.expand_path('queries/*.rb', __dir__)).sort.each(&method(:require))
|
data/lib/marc/spec.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Dir.glob(File.expand_path('spec/*.rb', __dir__)).sort.each(&method(:require))
|
2
|
+
|
3
|
+
module MARC
|
4
|
+
module Spec
|
5
|
+
class << self
|
6
|
+
def find(query_string, marc_record)
|
7
|
+
root = parse_query(query_string)
|
8
|
+
executor = Queries::QueryExecutor.new(marc_record, root)
|
9
|
+
executor.execute
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_query(query_string)
|
13
|
+
parse_tree = parser.parse(query_string, reporter: reporter)
|
14
|
+
xform.apply(parse_tree)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def parser
|
20
|
+
@parser ||= Parsing::Parser.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def xform
|
24
|
+
@xform ||= Queries::Transform.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def reporter
|
28
|
+
@reporter ||= Parslet::ErrorReporter::Contextual.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
inherit_from: ../.rubocop.yml
|
2
|
+
|
3
|
+
Style/ClassAndModuleChildren:
|
4
|
+
Enabled: false
|
5
|
+
|
6
|
+
Layout/LineLength:
|
7
|
+
Enabled: false
|
8
|
+
|
9
|
+
Metrics/BlockLength:
|
10
|
+
Enabled: false
|
11
|
+
|
12
|
+
Metrics/ClassLength:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
Metrics/ModuleLength:
|
16
|
+
Enabled: false
|
17
|
+
|
18
|
+
Metrics/MethodLength:
|
19
|
+
Enabled: false
|
data/rakelib/bundle.rake
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'ci/reporter/rake/rspec'
|
2
|
+
|
3
|
+
# Configure CI::Reporter report generation
|
4
|
+
ENV['GENERATE_REPORTS'] ||= 'true'
|
5
|
+
ENV['CI_REPORTS'] = 'artifacts/rspec'
|
6
|
+
|
7
|
+
desc 'Run all specs in spec directory, with coverage'
|
8
|
+
task coverage: ['ci:setup:rspec'] do
|
9
|
+
ENV['COVERAGE'] ||= 'true'
|
10
|
+
Rake::Task[:spec].invoke
|
11
|
+
end
|
data/rakelib/gem.rake
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems/gem_runner'
|
2
|
+
require 'marc/spec/module_info'
|
3
|
+
|
4
|
+
module MARC::Spec
|
5
|
+
class << self
|
6
|
+
def project_root
|
7
|
+
@project_root ||= File.expand_path('..', __dir__)
|
8
|
+
end
|
9
|
+
|
10
|
+
def artifacts_dir
|
11
|
+
return project_root unless ENV['CI']
|
12
|
+
|
13
|
+
@artifacts_dir ||= File.join(project_root, 'artifacts')
|
14
|
+
end
|
15
|
+
|
16
|
+
def gemspec_file
|
17
|
+
@gemspec_file ||= begin
|
18
|
+
gemspec_files = Dir.glob(File.expand_path('*.gemspec', project_root))
|
19
|
+
raise ArgumentError, "Too many .gemspecs: #{gemspec_files.join(', ')}" if gemspec_files.size > 1
|
20
|
+
raise ArgumentError, 'No .gemspec file found' if gemspec_files.empty?
|
21
|
+
|
22
|
+
gemspec_files[0]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def gemspec_basename
|
27
|
+
File.basename(gemspec_file)
|
28
|
+
end
|
29
|
+
|
30
|
+
def output_file
|
31
|
+
@output_file ||= begin
|
32
|
+
gem_name = File.basename(gemspec_file, '.*')
|
33
|
+
version = MARC::Spec::ModuleInfo::VERSION
|
34
|
+
basename = "#{gem_name}-#{version}.gem"
|
35
|
+
File.join(artifacts_dir, basename)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def output_file_relative
|
40
|
+
return File.basename(output_file) unless ENV['CI']
|
41
|
+
|
42
|
+
@output_file_relative ||= begin
|
43
|
+
artifacts_dir_relative = File.basename(artifacts_dir)
|
44
|
+
File.join(artifacts_dir_relative, File.basename(output_file))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Build #{MARC::Spec.gemspec_basename} as #{MARC::Spec.output_file_relative}"
|
51
|
+
task :gem do
|
52
|
+
args = ['build', MARC::Spec.gemspec_file, "--output=#{MARC::Spec.output_file}"]
|
53
|
+
Gem::GemRunner.new.run(args)
|
54
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubocop'
|
2
|
+
|
3
|
+
module ParserSpecs
|
4
|
+
module Formatter
|
5
|
+
include RuboCop::Cop::Util
|
6
|
+
|
7
|
+
def validity(v)
|
8
|
+
v ? 'valid' : 'invalid'
|
9
|
+
end
|
10
|
+
|
11
|
+
def quote(s)
|
12
|
+
to_string_literal(String.new(s))
|
13
|
+
end
|
14
|
+
|
15
|
+
def decamelize(str)
|
16
|
+
return unless str
|
17
|
+
|
18
|
+
str.gsub(/(?<!^)[A-Z]/) { "_#{$&}" }.downcase
|
19
|
+
end
|
20
|
+
|
21
|
+
def indent(str, indent, omit_first: true)
|
22
|
+
return str.gsub(/^/, indent) unless omit_first
|
23
|
+
|
24
|
+
str.gsub(/(\n)/, "\\1#{indent}")
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
include Formatter
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'parslet/rig/rspec'
|
3
|
+
|
4
|
+
module MARC
|
5
|
+
module Spec
|
6
|
+
module Parsing
|
7
|
+
context 'suite' do
|
8
|
+
describe <%= ":#{name}" %> do
|
9
|
+
let(:parser) { Parser.new }
|
10
|
+
let(:reporter) { Parslet::ErrorReporter::Deepest.new }
|
11
|
+
<% suites.sort.each do |suite| %>
|
12
|
+
describe <%= quote(suite.description) %> do
|
13
|
+
# <%= suite.json_path %><% (suite.tests_by_group[nil] || []).sort.each do |test| %>
|
14
|
+
it <%= quote("#{test.description} -> #{validity(test.valid)}") %> do
|
15
|
+
# <%= test.json_path %><% test.rspec_assertions.each do |asrt| %>
|
16
|
+
<%= asrt %><% end %>
|
17
|
+
end
|
18
|
+
<% end %>
|
19
|
+
<% suite.tests_by_group.each do |group, tests| %>
|
20
|
+
<% next unless group %>
|
21
|
+
describe <%= quote(group) %> do
|
22
|
+
<% tests.sort.each do |test| %>
|
23
|
+
it <%= quote("#{test.detail} -> #{validity(test.valid)}") %> do
|
24
|
+
# <%= test.json_path %><% test.rspec_assertions.each do |asrt| %>
|
25
|
+
<%= asrt %><% end %>
|
26
|
+
end
|
27
|
+
<% end %>
|
28
|
+
end
|
29
|
+
<% end %>
|
30
|
+
end<% end %>
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'ostruct'
|
3
|
+
require_relative 'formatter'
|
4
|
+
require_relative 'suite'
|
5
|
+
|
6
|
+
module ParserSpecs
|
7
|
+
class Rule
|
8
|
+
include Formatter
|
9
|
+
|
10
|
+
RULE_RE = %r{/(?<wild>wildCombination_)?(?<valid>valid|invalid)(?<rule>[A-Z][[:alpha:]]+)\.json$}.freeze
|
11
|
+
|
12
|
+
TEMPLATE_PATH = File.expand_path('parser_specs.rb.txt.erb', __dir__)
|
13
|
+
|
14
|
+
attr_reader :name
|
15
|
+
|
16
|
+
def initialize(name, suites = [])
|
17
|
+
@name = name
|
18
|
+
suites.each { |s| add_suite(s) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
name
|
23
|
+
end
|
24
|
+
|
25
|
+
def suites
|
26
|
+
@suites ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_suite(s)
|
30
|
+
return (suites << s) unless (existing = suites.find { |s1| s1.merge?(s) })
|
31
|
+
|
32
|
+
existing.merge(s)
|
33
|
+
end
|
34
|
+
|
35
|
+
def write_rspec_to(dir)
|
36
|
+
basename = "#{name}_spec.rb"
|
37
|
+
spec_path = File.join(dir, basename)
|
38
|
+
|
39
|
+
# ideally we could just run the ERB as-is, but it's hard to write
|
40
|
+
# a legible template that doesn't introduce some unwanted whitespace
|
41
|
+
spec_src = Rule.template.result(binding)
|
42
|
+
.gsub(/ +$/, '')
|
43
|
+
.gsub(/\n\n+/, "\n\n")
|
44
|
+
|
45
|
+
puts "writing #{basename}"
|
46
|
+
File.write(spec_path, spec_src)
|
47
|
+
end
|
48
|
+
|
49
|
+
class << self
|
50
|
+
include Formatter
|
51
|
+
|
52
|
+
def all_from_json(json_root)
|
53
|
+
Dir.glob(File.join(json_root, '*valid/*.json')).sort.each_with_object([]) do |json_path, rules|
|
54
|
+
rule_name, wild = extract_rule_metadata(json_path)
|
55
|
+
|
56
|
+
suite_data = JSON.parse(File.read(json_path), object_class: OpenStruct, symbolize_names: true)
|
57
|
+
suite = Suite.from_ostruct(suite_data, rule_name, wild, json_path.sub(json_root, ''))
|
58
|
+
|
59
|
+
add_suite(rules, rule_name, suite)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def template
|
64
|
+
@template ||= begin
|
65
|
+
template_src = File.read(TEMPLATE_PATH)
|
66
|
+
ERB.new(template_src, trim_mode: '-')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def extract_rule_metadata(json_path)
|
73
|
+
raise ArgumentError, "#{json_path} does not match #{RULE_RE.source}" unless (match_data = RULE_RE.match(json_path))
|
74
|
+
|
75
|
+
[normalize_rule_name(match_data[:rule]), !match_data[:wild].nil?]
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_suite(rules, rule_name, suite)
|
79
|
+
if (existing = rules.find { |r| r.name == rule_name })
|
80
|
+
existing.add_suite(suite)
|
81
|
+
else
|
82
|
+
rules << Rule.new(rule_name, [suite])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def normalize_rule_name(name)
|
87
|
+
return unless name
|
88
|
+
|
89
|
+
decamelize(name).tap do |n|
|
90
|
+
return 'subfield_char' if n == 'subfield_tag' # suite doesn't match spec
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|