relaton 3.0.0.pre.alpha.17 → 3.0.0.pre.alpha.19
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.
- checksums.yaml +4 -4
- data/data/equivalence/adoption-chains.yaml +59 -0
- data/lib/relaton/3gpp/CLAUDE.md +187 -0
- data/lib/relaton/3gpp/README.adoc +149 -0
- data/lib/relaton/adobe/CLAUDE.md +104 -0
- data/lib/relaton/bib/CLAUDE.md +130 -0
- data/lib/relaton/bib/README.adoc +330 -0
- data/lib/relaton/bib/model/item_shared.rb +5 -1
- data/lib/relaton/bib/model/series.rb +1 -1
- data/lib/relaton/bipm/CLAUDE.md +173 -0
- data/lib/relaton/bipm/README.adoc +352 -0
- data/lib/relaton/bipm/data_fetcher.rb +3 -2
- data/lib/relaton/bsi/CLAUDE.md +35 -0
- data/lib/relaton/bsi/README.adoc +112 -0
- data/lib/relaton/calconnect/CLAUDE.md +177 -0
- data/lib/relaton/calconnect/README.adoc +140 -0
- data/lib/relaton/ccsds/CLAUDE.md +74 -0
- data/lib/relaton/ccsds/README.adoc +154 -0
- data/lib/relaton/cen/CLAUDE.md +219 -0
- data/lib/relaton/cen/README.adoc +155 -0
- data/lib/relaton/cie/CLAUDE.md +90 -0
- data/lib/relaton/cie/README.adoc +121 -0
- data/lib/relaton/core/CLAUDE.md +140 -0
- data/lib/relaton/core/README.adoc +64 -0
- data/lib/relaton/core/data_fetcher.rb +142 -3
- data/lib/relaton/db/CLAUDE.md +373 -0
- data/lib/relaton/db.rb +21 -1
- data/lib/relaton/doi/CLAUDE.md +105 -0
- data/lib/relaton/doi/README.adoc +89 -0
- data/lib/relaton/easc/CLAUDE.md +85 -0
- data/lib/relaton/ecma/CLAUDE.md +217 -0
- data/lib/relaton/ecma/README.adoc +178 -0
- data/lib/relaton/equivalence.rb +117 -0
- data/lib/relaton/etsi/CLAUDE.md +219 -0
- data/lib/relaton/etsi/README.adoc +137 -0
- data/lib/relaton/evs/CLAUDE.md +120 -0
- data/lib/relaton/evs/README.adoc +27 -0
- data/lib/relaton/gb/CLAUDE.md +47 -0
- data/lib/relaton/gb/README.adoc +169 -0
- data/lib/relaton/gost/CLAUDE.md +100 -0
- data/lib/relaton/iala/CLAUDE.md +97 -0
- data/lib/relaton/iana/CLAUDE.md +105 -0
- data/lib/relaton/iana/README.adoc +107 -0
- data/lib/relaton/iec/CLAUDE.md +13 -0
- data/lib/relaton/iec/README.adoc +280 -0
- data/lib/relaton/iec/data_fetcher.rb +24 -8
- data/lib/relaton/iec/project_fetcher.rb +48 -0
- data/lib/relaton/iec/project_parser.rb +111 -0
- data/lib/relaton/iec.rb +5 -0
- data/lib/relaton/ieee/CLAUDE.md +176 -0
- data/lib/relaton/ieee/README.adoc +131 -0
- data/lib/relaton/ietf/CLAUDE.md +357 -0
- data/lib/relaton/ietf/README.adoc +167 -0
- data/lib/relaton/iho/CLAUDE.md +70 -0
- data/lib/relaton/iho/README.adoc +119 -0
- data/lib/relaton/index/CLAUDE.md +207 -0
- data/lib/relaton/index/README.adoc +221 -0
- data/lib/relaton/index/dataset_lock.rb +122 -0
- data/lib/relaton/index/family.rb +63 -0
- data/lib/relaton/index/type.rb +32 -0
- data/lib/relaton/index.rb +3 -0
- data/lib/relaton/isbn/CLAUDE.md +47 -0
- data/lib/relaton/isbn/README.adoc +91 -0
- data/lib/relaton/iso/CLAUDE.md +38 -0
- data/lib/relaton/iso/README.adoc +370 -0
- data/lib/relaton/iso/data_fetcher.rb +16 -1
- data/lib/relaton/itu/CLAUDE.md +685 -0
- data/lib/relaton/itu/README.adoc +188 -0
- data/lib/relaton/jcgm/CLAUDE.md +107 -0
- data/lib/relaton/jis/CLAUDE.md +64 -0
- data/lib/relaton/jis/README.adoc +167 -0
- data/lib/relaton/logger/CLAUDE.md +37 -0
- data/lib/relaton/logger/README.adoc +120 -0
- data/lib/relaton/nist/CLAUDE.md +180 -0
- data/lib/relaton/nist/README.adoc +389 -0
- data/lib/relaton/nist/bibliography.rb +17 -1
- data/lib/relaton/nist/data_fetcher.rb +60 -4
- data/lib/relaton/nist/hit_collection.rb +121 -59
- data/lib/relaton/nist/scraper.rb +21 -7
- data/lib/relaton/oasis/CLAUDE.md +251 -0
- data/lib/relaton/oasis/README.adoc +132 -0
- data/lib/relaton/ogc/CLAUDE.md +242 -0
- data/lib/relaton/ogc/README.adoc +134 -0
- data/lib/relaton/ogc/data_fetcher.rb +9 -3
- data/lib/relaton/oiml/CLAUDE.md +47 -0
- data/lib/relaton/oiml/README.adoc +70 -0
- data/lib/relaton/omg/CLAUDE.md +137 -0
- data/lib/relaton/omg/README.adoc +101 -0
- data/lib/relaton/plateau/CLAUDE.md +126 -0
- data/lib/relaton/plateau/README.adoc +139 -0
- data/lib/relaton/sdo/CLAUDE.md +81 -0
- data/lib/relaton/un/CLAUDE.md +91 -0
- data/lib/relaton/un/README.adoc +125 -0
- data/lib/relaton/version.rb +1 -1
- data/lib/relaton/w3c/CLAUDE.md +369 -0
- data/lib/relaton/w3c/README.adoc +140 -0
- data/lib/relaton/w3c/data_fetcher.rb +1 -1
- data/lib/relaton/xsf/CLAUDE.md +189 -0
- data/lib/relaton/xsf/README.adoc +127 -0
- data/lib/relaton.rb +1 -0
- metadata +85 -4
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
= Relaton::Ietf
|
|
2
|
+
|
|
3
|
+
_Formerly known as_ `rfcbib`.
|
|
4
|
+
|
|
5
|
+
Relaton::Ietf is a Ruby gem that searches and fetches standards from the https://www.ietf.org[Internet Engineering Task Force (IETF)], including RFCs.
|
|
6
|
+
|
|
7
|
+
The standards scrape form https://www.rfc-editor.org/search/rfc_search.php
|
|
8
|
+
|
|
9
|
+
== Usage
|
|
10
|
+
|
|
11
|
+
=== Fetching documents
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
require 'relaton/ietf'
|
|
16
|
+
=> true
|
|
17
|
+
|
|
18
|
+
# Fetch RFC document
|
|
19
|
+
item = Relaton::Ietf::Bibliography.get 'IETF RFC 8341'
|
|
20
|
+
relaton-ietf] INFO: (IETF RFC 8341) Fetching from Relaton repository ...
|
|
21
|
+
[relaton-ietf] INFO: (IETF RFC 8341) Found: `RFC 8341`
|
|
22
|
+
=> #<Relaton::Ietf::ItemData:0x0000000125608cb0
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
# Fetch Internet-Draft document
|
|
26
|
+
Relaton::Ietf::Bibliography.get 'IETF I-D.draft-abarth-cake-01'
|
|
27
|
+
[relaton-ietf] INFO: (IETF I-D.draft-abarth-cake-01) Fetching from Relaton repository ...
|
|
28
|
+
[relaton-ietf] INFO: (IETF I-D.draft-abarth-cake-01) Found: `draft-abarth-cake-01`
|
|
29
|
+
=> #<Relaton::Ietf::ItemData:0x000000012141fdd0
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
# Return nil if a document doesn't exist.
|
|
33
|
+
Relaton::Ietf::Bibliography.get 'IETF 1111'
|
|
34
|
+
[relaton-ietf] INFO: (IETF 1111) Fetching from Relaton repository ...
|
|
35
|
+
[relaton-ietf] INFO: (IETF 1111) Not found.
|
|
36
|
+
=> nil
|
|
37
|
+
----
|
|
38
|
+
|
|
39
|
+
=== Serialization
|
|
40
|
+
|
|
41
|
+
[source,ruby]
|
|
42
|
+
----
|
|
43
|
+
item.to_xml
|
|
44
|
+
=> "<bibitem id="RFC8341" type="standard" schema-version="v1.4.1">
|
|
45
|
+
<fetched>2026-02-18</fetched>
|
|
46
|
+
<title type="main">Network Configuration Access Control Model</title>
|
|
47
|
+
<uri type="src">https://www.rfc-editor.org/info/rfc8341</uri>
|
|
48
|
+
<docidentifier type="IETF" primary="true">RFC 8341</docidentifier>
|
|
49
|
+
....
|
|
50
|
+
</bibitem>"
|
|
51
|
+
----
|
|
52
|
+
With `bibdata: true` option XML output is wrapped with `bibdata` element and `ext` element added.
|
|
53
|
+
[source,ruby]
|
|
54
|
+
----
|
|
55
|
+
item.to_xml bibdata: true
|
|
56
|
+
=> "<bibdata type="standard" schema-version="v1.4.1">
|
|
57
|
+
<fetched>2026-02-18</fetched>
|
|
58
|
+
<title type="main">Network Configuration Access Control Model</title>
|
|
59
|
+
<uri type="src">https://www.rfc-editor.org/info/rfc8341</uri>
|
|
60
|
+
<docidentifier type="IETF" primary="true">RFC 8341</docidentifier>
|
|
61
|
+
...
|
|
62
|
+
<ext schema-version="v1.0.1">
|
|
63
|
+
<doctype>rfc</doctype>
|
|
64
|
+
<flavor>ietf</flavor>
|
|
65
|
+
<stream>IETF</stream>
|
|
66
|
+
</ext>
|
|
67
|
+
</bibdata>"
|
|
68
|
+
----
|
|
69
|
+
|
|
70
|
+
=== Typed source links
|
|
71
|
+
|
|
72
|
+
IETF documents may have `src`, `xml`, and `doi` source link types.
|
|
73
|
+
|
|
74
|
+
* `src` - web publication
|
|
75
|
+
* `xml` - BibXML publication
|
|
76
|
+
* `doi` - DOI reference
|
|
77
|
+
|
|
78
|
+
[source,ruby]
|
|
79
|
+
----
|
|
80
|
+
item.source.first.type
|
|
81
|
+
=> "src"
|
|
82
|
+
item.source.first.content
|
|
83
|
+
=> "https://www.rfc-editor.org/info/rfc8341"
|
|
84
|
+
----
|
|
85
|
+
|
|
86
|
+
=== Parse a file locally
|
|
87
|
+
|
|
88
|
+
[source,ruby]
|
|
89
|
+
----
|
|
90
|
+
item = Relaton::Ietf::BibXMLParser.parse_rfc File.read("spec/ietf/fixtures/rfc.xml")
|
|
91
|
+
=> #<Relaton::Ietf::ItemData:0x000000012913e4d8
|
|
92
|
+
...
|
|
93
|
+
----
|
|
94
|
+
|
|
95
|
+
=== Create bibliographic item from YAML
|
|
96
|
+
[source,ruby]
|
|
97
|
+
----
|
|
98
|
+
Relaton::Ietf::Item.from_yaml File.read("spec/ietf/fixtures/item.yaml")
|
|
99
|
+
=> #<Relaton::Ietf::ItemData:0x0000000127edf508
|
|
100
|
+
...
|
|
101
|
+
----
|
|
102
|
+
|
|
103
|
+
=== Fetch data
|
|
104
|
+
|
|
105
|
+
There are IETF datasets that can be converted into RelatonXML/BibXML/BibYAML formats:
|
|
106
|
+
|
|
107
|
+
- `ietf-rfcsubseries` - https://www.rfc-editor.org/rfc-index.xml (`<bcp-entry>`, `<fyi-entry>`, `<std-entry>`)
|
|
108
|
+
- `ietf-internet-drafts` - convert files from local directory `./bibxml-ids`. Use `rsync -avcizxL rsync.ietf.org::bibxml-ids ./bibxml-ids` command to fetch the files.
|
|
109
|
+
- `ietf-rfc-entries` - https://www.rfc-editor.org/rfc-index.xml (`<rfc-entry>`)
|
|
110
|
+
|
|
111
|
+
The method `Relaton::Ietf::DataFetcher.fetch(source, output: "data", format: "yaml")` converts all the documents from the dataset and saves them to the `./data` folder in YAML format.
|
|
112
|
+
|
|
113
|
+
Arguments:
|
|
114
|
+
|
|
115
|
+
- `source` - dataset name (`ietf-rfcsubseries` or `ietf-internet-drafts`)
|
|
116
|
+
- `output` - folder to save documents (default './data').
|
|
117
|
+
- `format` - the format in which the documents are saved. Possible formats are: `yaml`, `xml`, `bibxml` (default `yaml`).
|
|
118
|
+
|
|
119
|
+
[source,ruby]
|
|
120
|
+
----
|
|
121
|
+
Relaton::Ietf::DataFetcher.fetch "ietf-internet-drafts"
|
|
122
|
+
Started at: 2021-12-17 10:23:20 +0100
|
|
123
|
+
Stopped at: 2021-12-17 10:29:19 +0100
|
|
124
|
+
Done in: 1360 sec.
|
|
125
|
+
=> nil
|
|
126
|
+
----
|
|
127
|
+
|
|
128
|
+
=== The index
|
|
129
|
+
|
|
130
|
+
Lookups read one combined index — `index-v2.zip` from
|
|
131
|
+
https://github.com/relaton/relaton-data-ietf[relaton-data-ietf], covering RFCs,
|
|
132
|
+
the RFC sub-series and Internet-Drafts in ~177k rows. `DataFetcher` writes the same
|
|
133
|
+
shape as it crawls, one row per record keyed by that record's primary
|
|
134
|
+
docidentifier, serialised as a `Pubid::Ietf::Identifier`:
|
|
135
|
+
|
|
136
|
+
[source,yaml]
|
|
137
|
+
----
|
|
138
|
+
- :id:
|
|
139
|
+
_type: pubid:ietf:rfc
|
|
140
|
+
number: '3986'
|
|
141
|
+
:file: data/rfc3986.yaml
|
|
142
|
+
----
|
|
143
|
+
|
|
144
|
+
A record whose identifier `Pubid::Ietf` cannot parse is still written, but is left
|
|
145
|
+
out of the index and logged — a single unparseable row would otherwise make the
|
|
146
|
+
whole index unloadable for every consumer. The total is restated when the crawl
|
|
147
|
+
finishes.
|
|
148
|
+
|
|
149
|
+
Note sub-series ids key to their own record: `STD 66` resolves to the STD document,
|
|
150
|
+
whose `relation: includes` names the RFC. There is no single RFC to alias to in
|
|
151
|
+
general — 42 of the 365 sub-series include more than one.
|
|
152
|
+
|
|
153
|
+
=== Logging
|
|
154
|
+
|
|
155
|
+
Relaton::Ietf uses the relaton-logger gem for logging. By default, it logs to STDOUT. To change the log levels and add other loggers, read the https://github.com/relaton/relaton-logger#usage[relaton-logger] documentation.
|
|
156
|
+
|
|
157
|
+
== Development
|
|
158
|
+
|
|
159
|
+
The suite uses one cached index fixture, `spec/ietf/fixtures/ietf-index-v2.zip`, copied wholesale from https://github.com/relaton/relaton-data-ietf[relaton-data-ietf]. Refresh it by re-downloading that repo's `index-v2.zip` when a document lookup starts 404ing — a stale index names files that no longer exist, which surfaces as a 404 rather than as an index error.
|
|
160
|
+
|
|
161
|
+
== Contributing
|
|
162
|
+
|
|
163
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/metanorma/relaton-ietf.
|
|
164
|
+
|
|
165
|
+
== License
|
|
166
|
+
|
|
167
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What This Is
|
|
6
|
+
|
|
7
|
+
relaton-iho is a Ruby gem that fetches and serializes IHO (International Hydrographic Organization) standards metadata. It is a "flavor" gem in the Relaton ecosystem, extending relaton-bib with IHO-specific bibliographic data models.
|
|
8
|
+
|
|
9
|
+
## Common Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bundle exec rspec # run all tests
|
|
13
|
+
bundle exec rspec spec/relaton/iho/item_spec.rb # run a single spec file
|
|
14
|
+
bundle exec rspec spec/relaton/iho/item_spec.rb:15 # run a single example by line
|
|
15
|
+
bundle exec rubocop # lint
|
|
16
|
+
bundle exec rubocop -a # lint with auto-fix
|
|
17
|
+
bundle exec rake install # install gem locally
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Architecture
|
|
21
|
+
|
|
22
|
+
### Flavor Pattern
|
|
23
|
+
|
|
24
|
+
This gem follows the Relaton flavor pattern: it subclasses base classes from `relaton-bib` under the `Relaton::Iho` namespace. The key mechanism is `NamespaceHelper` (from relaton-bib), which dynamically resolves the module namespace from the class name. This means `Relaton::Iho::ItemData#to_xml` automatically delegates to `Relaton::Iho::Bibdata.to_xml` (not `Relaton::Bib::Bibdata`).
|
|
25
|
+
|
|
26
|
+
Core model classes and their base classes:
|
|
27
|
+
- `Item < Bib::Item` — main bibliographic item, uses `model ItemData`
|
|
28
|
+
- `ItemData < Bib::ItemData` — data container enabling namespace-aware `to_xml`/`to_yaml`/`to_json`
|
|
29
|
+
- `ItemBase < Item` — stripped-down item for use inside relations (no id, schema_version, fetched, ext)
|
|
30
|
+
- `Bibitem < Item` — XML bibitem serialization (includes `Bib::BibitemShared`)
|
|
31
|
+
- `Bibdata < Item` — XML bibdata serialization (includes `Bib::BibdataShared`)
|
|
32
|
+
- `Relation < Bib::Relation` — overrides `bibitem` attribute to use `Iho::ItemBase`
|
|
33
|
+
- `Doctype < Bib::Doctype` — IHO document type vocabulary
|
|
34
|
+
- `Ext < Bib::Ext` — IHO-specific extension data (doctype, commentperiod)
|
|
35
|
+
- `Processor < Core::Processor` — Relaton processor for registry integration
|
|
36
|
+
- `HashParserV1` — converts v1-format YAML/Hash data to v2 model objects (required on demand, not auto-loaded)
|
|
37
|
+
- `Bibliography` — fetches IHO data from relaton-data-iho GitHub repo
|
|
38
|
+
|
|
39
|
+
All model classes use Lutaml::Model for declarative attribute/serialization definitions.
|
|
40
|
+
|
|
41
|
+
### HashParserV1
|
|
42
|
+
|
|
43
|
+
`HashParserV1` (in `hash_parser_v1.rb`) handles conversion of legacy v1 data to the current model. It overrides the base `Bib::HashParserV1` to handle IHO-specific structures:
|
|
44
|
+
- **editorialgroup**: IHO v1 data has nested committee arrays (`editorialgroup: [[{committee: ...}, ...]]`). Each committee is converted to a `contributor` with `role type="author"` / `description="committee"`, organization "International Hydrographic Organization"/"IHO", and nested `subdivision` elements for sub-committees.
|
|
45
|
+
- **commentperiod**: Moved from top-level into `ext`.
|
|
46
|
+
- Factory methods (`bib_item`, `create_doctype`, `create_relation`) return IHO-specific classes.
|
|
47
|
+
|
|
48
|
+
This module is required on demand (`require "relaton/iho/hash_parser_v1"`) and not auto-loaded by the main `relaton/iho` entry point.
|
|
49
|
+
|
|
50
|
+
### Load Order Constraint
|
|
51
|
+
|
|
52
|
+
In `item.rb`, `require_relative "relation"` is placed **after** the `Item` class definition because `Relation` → `ItemBase` → `Item` creates a circular dependency if loaded before `Item` exists.
|
|
53
|
+
|
|
54
|
+
### Data Retrieval
|
|
55
|
+
|
|
56
|
+
`Bibliography.search`/`.get` fetches YAML from the relaton-data-iho GitHub repo via `Relaton::Index`, then deserializes into model objects.
|
|
57
|
+
|
|
58
|
+
### Test Infrastructure
|
|
59
|
+
|
|
60
|
+
- **VCR cassettes** (`spec/vcr_cassettes/`): recorded HTTP responses for offline testing
|
|
61
|
+
- **RelaxNG validation**: specs validate XML output against `grammars/relaton-iho-compile.rng` using ruby-jing
|
|
62
|
+
- **Fixtures** (`spec/fixtures/`): reference XML/YAML documents for round-trip serialization tests
|
|
63
|
+
|
|
64
|
+
### IHO-Specific Document Types
|
|
65
|
+
|
|
66
|
+
policy-and-procedures, best-practices, supporting-document, report, legal, directives, proposal, standard
|
|
67
|
+
|
|
68
|
+
## Testing
|
|
69
|
+
|
|
70
|
+
- **Index fixture:** `spec/fixtures/index-v1.zip` is pre-loaded into `Relaton::Index` pool in `before(:suite)` (configured in `spec/support/webmock.rb`). Run `rake spec:update_index` to refresh from relaton-data-iho.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
= Relaton for IHO documents
|
|
2
|
+
|
|
3
|
+
Relaton::Iho is a Ruby gem that implements the https://github.com/metanorma/metanorma-model-iso#iso-bibliographic-item[IsoBibliographicItem model].
|
|
4
|
+
|
|
5
|
+
It currently retrieves metadata of IHO Standards from https://github.com/relaton/relaton-data-iho.
|
|
6
|
+
|
|
7
|
+
== Usage
|
|
8
|
+
|
|
9
|
+
=== Search for a standard using keywords
|
|
10
|
+
|
|
11
|
+
Reference can be specified with or without an edition. References without an edition will return the latest edition of the standard. References with an edition will return the standard with the specified edition.
|
|
12
|
+
|
|
13
|
+
[source,ruby]
|
|
14
|
+
----
|
|
15
|
+
require 'relaton/iho'
|
|
16
|
+
=> true
|
|
17
|
+
|
|
18
|
+
# Search for a standard using a reference without an edition
|
|
19
|
+
item = Relaton::Iho::Bibliography.search("IHO B-11")
|
|
20
|
+
[relaton-iho] INFO: (IHO B-11) Fetching from Relaton repository ...
|
|
21
|
+
[relaton-iho] INFO: (IHO B-11) Found: `B-11`
|
|
22
|
+
=> #<Relaton::Iho::ItemData:0x0000000121f85aa8...
|
|
23
|
+
|
|
24
|
+
# Search for a standard using a reference with an edition
|
|
25
|
+
item = Relaton::Iho::Bibliography.search("IHO B-11 1.0.0")
|
|
26
|
+
[relaton-iho] INFO: (IHO B-11 1.0.0) Fetching from Relaton repository ...
|
|
27
|
+
[relaton-iho] INFO: (IHO B-11 1.0.0) Found: `B-11`
|
|
28
|
+
=> #<Relaton::Iho::ItemData:0x0000000125b5bb90
|
|
29
|
+
...
|
|
30
|
+
----
|
|
31
|
+
|
|
32
|
+
=== XML serialization
|
|
33
|
+
[source,ruby]
|
|
34
|
+
----
|
|
35
|
+
item.to_xml
|
|
36
|
+
=> "<bibitem id="B11" type="standard" schema-version="v1.4.1">
|
|
37
|
+
<fetched>2026-02-20</fetched>
|
|
38
|
+
<title language="en" type="main">IHO-IOC GEBCO Cook Book</title>
|
|
39
|
+
<title language="fr" type="main">Livre de recettes GEBCO OHI-COI</title>
|
|
40
|
+
<uri type="pdf">https://www.star.nesdis.noaa.gov/socd/lsa/GEBCO_Cookbook/documents/CookBook_20191031.pdf</uri>
|
|
41
|
+
<docidentifier type="IHO" primary="true">B-11</docidentifier>
|
|
42
|
+
...
|
|
43
|
+
</bibitem>"
|
|
44
|
+
----
|
|
45
|
+
With argument `bibdata: true` it outputs XML wrapped by `bibdata` element and adds flavor `ext` element.
|
|
46
|
+
[source,ruby]
|
|
47
|
+
----
|
|
48
|
+
item.to_xml bibdata: true
|
|
49
|
+
=> "<bibdata type="standard" schema-version="v1.4.1">
|
|
50
|
+
<fetched>2026-02-20</fetched>
|
|
51
|
+
<title language="en" type="main">IHO-IOC GEBCO Cook Book</title>
|
|
52
|
+
<title language="fr" type="main">Livre de recettes GEBCO OHI-COI</title>
|
|
53
|
+
<uri type="pdf">https://www.star.nesdis.noaa.gov/socd/lsa/GEBCO_Cookbook/documents/CookBook_20191031.pdf</uri>
|
|
54
|
+
<docidentifier type="IHO" primary="true">B-11</docidentifier>
|
|
55
|
+
...
|
|
56
|
+
<ext schema-version="v1.0.0">
|
|
57
|
+
<flavor>iho</flavor>
|
|
58
|
+
</ext>
|
|
59
|
+
</bibdata>"
|
|
60
|
+
----
|
|
61
|
+
|
|
62
|
+
=== Get code
|
|
63
|
+
[source,ruby]
|
|
64
|
+
----
|
|
65
|
+
Relaton::Iho::Bibliography.get "IHO B-11"
|
|
66
|
+
[relaton-iho] INFO: (IHO B-11) Fetching from Relaton repository ...
|
|
67
|
+
[relaton-iho] INFO: (IHO B-11) Found: `B-11`
|
|
68
|
+
=> #<Relaton::Iho::ItemData:0x0000000125f530e0
|
|
69
|
+
...
|
|
70
|
+
----
|
|
71
|
+
|
|
72
|
+
=== Typed links
|
|
73
|
+
|
|
74
|
+
Each IHO document has `html`, `website`, or `pdf` type link.
|
|
75
|
+
|
|
76
|
+
[source,ruby]
|
|
77
|
+
----
|
|
78
|
+
item.source.first.type
|
|
79
|
+
=> "pdf"
|
|
80
|
+
|
|
81
|
+
item.source.first.content
|
|
82
|
+
=> "https://www.star.nesdis.noaa.gov/socd/lsa/GEBCO_Cookbook/documents/CookBook_20191031.pdf"
|
|
83
|
+
----
|
|
84
|
+
|
|
85
|
+
=== Create bibliographic item from XML
|
|
86
|
+
[source,ruby]
|
|
87
|
+
----
|
|
88
|
+
Relaton::Iho::Item.from_xml File.read('spec/fixtures/bibdata.xml')
|
|
89
|
+
=> #<Relaton::Iho::ItemData:0x0000000125f56920
|
|
90
|
+
...
|
|
91
|
+
----
|
|
92
|
+
|
|
93
|
+
=== Create bibliographic item from YAML
|
|
94
|
+
[source,ruby]
|
|
95
|
+
----
|
|
96
|
+
Relaton::Iho::Item.from_yaml File.read('spec/fixtures/item.yaml')
|
|
97
|
+
=> #<Relaton::Iho::ItemData:0x000000012274ac98
|
|
98
|
+
...
|
|
99
|
+
----
|
|
100
|
+
|
|
101
|
+
=== Logging
|
|
102
|
+
|
|
103
|
+
RelatonIho uses the relaton-logger gem for logging. By default, it logs to STDOUT. To change the log levels and add other loggers, read the https://github.com/relaton/relaton-logger#usage[relaton-logger] documentation.
|
|
104
|
+
|
|
105
|
+
== Development
|
|
106
|
+
|
|
107
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
108
|
+
|
|
109
|
+
To update the index test fixture (used by tests), run `rake spec:update_index`. This downloads the latest `index-v1.zip` from the https://github.com/relaton/relaton-data-iho[relaton-data-iho] repository.
|
|
110
|
+
|
|
111
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to https://rubygems.org[rubygems.org].
|
|
112
|
+
|
|
113
|
+
== Contributing
|
|
114
|
+
|
|
115
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/relaton/relaton-iho.
|
|
116
|
+
|
|
117
|
+
== License
|
|
118
|
+
|
|
119
|
+
The gem is available as open source under the terms of the https://opensource.org/licenses/MIT[MIT License].
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
relaton-index is a Ruby gem that provides indexing and searching of Relaton document references. It maps document identifiers to file paths, supporting both local index creation (for publishing) and remote index consumption (downloading from URLs with 24-hour caching).
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Run all tests (default rake task)
|
|
13
|
+
rake spec
|
|
14
|
+
|
|
15
|
+
# Run linting
|
|
16
|
+
rake rubocop
|
|
17
|
+
|
|
18
|
+
# Run specific test file
|
|
19
|
+
bundle exec rspec spec/relaton/type_spec.rb
|
|
20
|
+
|
|
21
|
+
# Run specific test by name
|
|
22
|
+
bundle exec rspec spec/relaton/file_io_spec.rb -e "fetch_and_save"
|
|
23
|
+
|
|
24
|
+
# Install dependencies
|
|
25
|
+
bin/setup
|
|
26
|
+
|
|
27
|
+
# Interactive console
|
|
28
|
+
bin/console
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
|
|
33
|
+
### Core Classes (all under `Relaton::Index` module in `lib/relaton/index/`)
|
|
34
|
+
|
|
35
|
+
- **`Relaton::Index`** (module, `lib/relaton/index.rb`) — Static API entry point. Delegates to Pool and Config. Main methods: `find_or_create`, `close`, `configure`.
|
|
36
|
+
|
|
37
|
+
- **Pool** — Object pool that caches Type instances by document type (`:ISO`, `:IEC`, `:IHO`, etc.). Reuses existing indexes if parameters match, recreates if they change.
|
|
38
|
+
|
|
39
|
+
- **Type** — Represents one index for a document type. Holds an array of `{id:, file:}` hashes. Provides `add_or_update`, `search` (subset match, string substring match, or block), and `save`.
|
|
40
|
+
|
|
41
|
+
- **FileIO** — Handles reading/writing/downloading index files. Three modes based on `@url`: string URL (download and cache to `~/.relaton/{type}/`), `true` (read local file from `~/.relaton/{type}/`), `nil` (read from current directory). Uses class-level Mutex for thread-safe downloads. Validates index format on load.
|
|
42
|
+
|
|
43
|
+
- **ShardSource** (`shard_source.rb`) — Reads the machine index on a data repo's Pages site (relaton#189): `index/manifest.json`, then the one `index/shard-NNNNN.json` at `Zlib.crc32(id.root.number.to_s) % shards`. Memory only, 24 h TTL, one `Mutex`. Built by `Type` when `find_or_create` gets `pages_url:`. Reuses `FileIO#deserialize_id`/`#deserialize_pubid` through a file-less `FileIO`.
|
|
44
|
+
|
|
45
|
+
- **FileStorage** — Storage abstraction module with `ctime`, `read`, `write`, `remove`. Can be replaced via `Config.storage=` for custom backends (e.g., S3).
|
|
46
|
+
|
|
47
|
+
- **Config** — Global configuration: `storage`, `storage_dir`, `filename` (default: "index.yaml").
|
|
48
|
+
|
|
49
|
+
### Data Flow
|
|
50
|
+
|
|
51
|
+
1. `Relaton::Index.find_or_create(:TYPE, url:, file:, pubid_class:, pages_url:)` → Pool looks up or creates Type (`id_keys:` is accepted for one release with a deprecation warning, then ignored)
|
|
52
|
+
2. Type lazily loads index via FileIO on first access
|
|
53
|
+
3. FileIO either reads local YAML or downloads ZIP from URL, extracts, validates format
|
|
54
|
+
4. Search matches against `:id` field (string comparison via `include?` or custom block)
|
|
55
|
+
5. `save` writes index as YAML to local file
|
|
56
|
+
|
|
57
|
+
With `pages_url:` (W3C, the #189 pilot) steps 2–3 change: a non-String query
|
|
58
|
+
goes to `ShardSource#rows` and reads one shard, and `Type#index` (a String query,
|
|
59
|
+
a block-only search) is `ShardSource#whole_index`, the `<index>.zip` that the
|
|
60
|
+
manifest names, read from the Pages site. No `FileIO#read`, no disk cache.
|
|
61
|
+
|
|
62
|
+
### What `#search` matches by default
|
|
63
|
+
|
|
64
|
+
Search is narrow, then match. The **match** step, for two identifiers, is
|
|
65
|
+
pubid's asymmetric subset match `id === item[:id]` (`Pubid::SubsetMatch`): the
|
|
66
|
+
query is the reference, and a component it leaves nil or empty matches any
|
|
67
|
+
value. That is what a search by reference means, so a flavor whose optional
|
|
68
|
+
components are true wildcards passes **no block** — OASIS, W3C, XSF, IALA,
|
|
69
|
+
OGC, ECMA, 3GPP, CalConnect, GOST and Plateau each call
|
|
70
|
+
`index.search(pubid)`. A component that a flavor reads as "none" when nil is
|
|
71
|
+
declared `subset_strict` in pubid (pubid#408), so `===` compares it exactly.
|
|
72
|
+
|
|
73
|
+
A nil is a wildcard here, not "the document has none". A caller that needs
|
|
74
|
+
**exact** equality says so with `search(id, exact: true)`, which selects the
|
|
75
|
+
rows with `item[:id] == id`. The binary-search narrowing still runs first.
|
|
76
|
+
These callers do:
|
|
77
|
+
|
|
78
|
+
| caller | why |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `Relaton::Ccsds::HitCollection#rows` (edition branch) | pubid's CCSDS `suffix` is a wildcard, so `CCSDS 101.0-B-4` would also reach the historical `101.0-B-4-S` — 260 such pairs in the index fixture. (The omitted language reached the translations too, until pubid#408 made `language` `subset_strict`.) |
|
|
81
|
+
| `Relaton::Ietf::Scraper#fetch_doc` | a draft slug omits the version, so `draft-foo` would match every `draft-foo-NN` and `.first` would pick one — 20,513 such pairs in the first 40k fixture rows |
|
|
82
|
+
| `Relaton::Gost::Bibliography#rows` (dated citation) | a dated citation names one edition; it replaces the old `query.matches?(row)` with no ignore list, which is `==` |
|
|
83
|
+
| `Relaton::Plateau::HitCollection#find` (reference with an edition) | a reference with an edition names that one row, as the old `row[:id] == ref` block did |
|
|
84
|
+
| `Relaton::Oiml::Bibliography#best_row` (a Bulletin) | a Bulletin's year, issue and sequence are its locator, so `OIML Bulletin 1960` is the volume and not every article of 1960 |
|
|
85
|
+
|
|
86
|
+
A String query keeps the substring match it always had, in both directions;
|
|
87
|
+
with `exact: true` it is compared with `==`. A block is a custom predicate for
|
|
88
|
+
any other rule, and `search` raises `ArgumentError` if it gets both a block and
|
|
89
|
+
`exact: true`.
|
|
90
|
+
|
|
91
|
+
### The narrowing key — one expression, its call sites
|
|
92
|
+
|
|
93
|
+
Search is two-stage: narrow, then match. Narrowing binary-searches the index for
|
|
94
|
+
the run of entries whose **base document number** equals the query's, using the key
|
|
95
|
+
`id.root.number.to_s` (`#root` walks a supplement/amendment `.base` chain, so a
|
|
96
|
+
document and its wrappers share one key and cluster together).
|
|
97
|
+
|
|
98
|
+
**Narrowing only happens for non-String queries on a `pubid_class:` index** —
|
|
99
|
+
`search_candidates` loads the raw side first (that load is what settles
|
|
100
|
+
`@file_io.sorted`), then narrows. A String query scans the whole index
|
|
101
|
+
*and* matches via `item[:id].to_s.include?(id)`, which renders every pubid in it.
|
|
102
|
+
So a flavor on a `pubid_class:` index must query with parsed identifiers; querying
|
|
103
|
+
it with strings is slower than the plain-string index it replaced.
|
|
104
|
+
|
|
105
|
+
That expression is written out in these places, and they must all agree or
|
|
106
|
+
bsearch silently returns the wrong slice:
|
|
107
|
+
|
|
108
|
+
| file | method | role |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `type.rb` | `candidates_by_number` | the query's key |
|
|
111
|
+
| `type.rb` | `bsearch_left` / `bsearch_right` | bounds, over the **precomputed keys array** |
|
|
112
|
+
| `type.rb` | `narrowing_key` | add-time key (root-guarded for String ids) |
|
|
113
|
+
| `file_io.rb` | `deserialize_pubid` | build-time sort of the materialized rows |
|
|
114
|
+
| `file_io.rb` | `warn_unless_sorted` | sortedness check |
|
|
115
|
+
| `file_io.rb` | `sort_structured_index` / `save_raw` | save-time sort |
|
|
116
|
+
|
|
117
|
+
### Raw rows and the sidecar (relaton#242 stopgap)
|
|
118
|
+
|
|
119
|
+
`FileIO#read_raw` returns `[keys, rows]`: the rows as plain `to_hash` hashes —
|
|
120
|
+
no pubid objects — with a parallel array of precomputed root-number keys, sorted.
|
|
121
|
+
A **Marshal sidecar** (`<index>.yaml.ms`, `SIDECAR_VERSION`-stamped) holds them,
|
|
122
|
+
so a repeat load skips the YAML parse and the one-time full materialization that
|
|
123
|
+
computes the keys (objects exist only during that build; the raw hash cannot
|
|
124
|
+
walk `.base` itself). The sidecar is authoritative while it is at least as new
|
|
125
|
+
as the yaml — the yaml is not parsed until the sidecar is stale or unreadable —
|
|
126
|
+
and `save`/`remove` delete it. A parsed search materializes pubid objects only
|
|
127
|
+
for its bucket (`FileIO#materialize`); the whole graph materializes only through
|
|
128
|
+
`Type#index`, a String query, or a block. `add_or_update` stores raw rows
|
|
129
|
+
(`to_hash` when the id is a `pubid_class` instance, verbatim otherwise) with an
|
|
130
|
+
O(1) raw-side dedup by id-hash equality. Numbers: the 79,993-row ISO index is
|
|
131
|
+
~311 MB of raw rows instead of a ~675 MB object graph; only the searched bucket
|
|
132
|
+
is materialized at search time.
|
|
133
|
+
|
|
134
|
+
**Consequence for pubid flavors:** once a flavor does query with parsed ids, an
|
|
135
|
+
identifier family whose `number` is nil keys every row to `""`, so they collapse
|
|
136
|
+
into one bucket and the bsearch buys nothing. It fails *silently* — results stay
|
|
137
|
+
correct, only speed drops. IETF Internet-Drafts are the worked example (see
|
|
138
|
+
`docs/data-repository-format.adoc`, "Two obligations a pubid-backed index
|
|
139
|
+
carries"); the fix belongs upstream, giving the family a real `number`, rather than
|
|
140
|
+
special-casing the key here.
|
|
141
|
+
|
|
142
|
+
### Index Format
|
|
143
|
+
|
|
144
|
+
YAML array of hashes with `:id` (string or structured hash) and `:file` (path string). Supports backward compatibility with old string-based format and newer pubid object format. The full data-repository/index specification (schema, `:id` v1/v2 shapes, publishing + GitHub Pages contract) lives in `docs/data-repository-format.adoc` at the repo root.
|
|
145
|
+
|
|
146
|
+
### Flavor `INDEXFILE` convention
|
|
147
|
+
|
|
148
|
+
Each flavor names its published index via a single constant in its top-level
|
|
149
|
+
`lib/relaton/<flavor>.rb`:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
INDEXFILE = "index-vN".freeze # base name only — no extension
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Rules:**
|
|
156
|
+
- The constant is spelled **`INDEXFILE`** (one word, no underscore) and holds the
|
|
157
|
+
**base name without extension** — `"index-v1"`, `"index-v2"`, etc.
|
|
158
|
+
- Call sites append the extension: `"#{INDEXFILE}.yaml"` for the local file passed
|
|
159
|
+
as `file:`, `"#{INDEXFILE}.zip"` for the published artifact appended to the
|
|
160
|
+
consumer `url:`. Never embed `.yaml`/`.zip` in the constant, and never hardcode
|
|
161
|
+
`index-vN.zip` at a call site — derive it from `INDEXFILE` so a version bump is
|
|
162
|
+
a one-line change.
|
|
163
|
+
- The version number encodes the index **structure** (`v1` = plain-string `:id`,
|
|
164
|
+
`v2` = pubid-hash `:id`); bumping it lets the previous gem line keep reading the
|
|
165
|
+
old file while the new structure is published under a new name.
|
|
166
|
+
|
|
167
|
+
### What each flavor call site passes
|
|
168
|
+
|
|
169
|
+
A pubid flavor calls `find_or_create` from three places, and they do not need
|
|
170
|
+
the same arguments:
|
|
171
|
+
|
|
172
|
+
| call site | `url:` | `pubid_class:` |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| producer — `DataFetcher#index` | none (`file:` is CWD-relative) | **yes** — `FileIO#save` calls `to_hash` only for instances of it |
|
|
175
|
+
| consumer — `Bibliography`/`HitCollection#index` | the published `.zip` | **yes** — without it the rows stay raw hashes and `Type#search` stops narrowing |
|
|
176
|
+
| consumer on the Pages shards (W3C) | none; **`pages_url:`** | **yes** — a shard row is deserialized with it |
|
|
177
|
+
| delete — `Processor#remove_index_file` | **`true`** | **no** |
|
|
178
|
+
|
|
179
|
+
The delete path is `Type#remove_file` → `FileIO#remove` →
|
|
180
|
+
`FileStorage.remove(file)`. `FileIO#file` is built from `url`, the type's
|
|
181
|
+
directory and `file:` only, and the delete never reads or deserializes the
|
|
182
|
+
index, so `pubid_class:` has no effect on it. `url:` does: without it `file` is
|
|
183
|
+
the bare name, so a `Db#clear` on an empty pool deletes `./index-vN.yaml` in the
|
|
184
|
+
working directory and leaves `~/.relaton/<type>/` in place. OASIS shipped that
|
|
185
|
+
bug until `url: true` was added. `spec/index/relaton/type_spec.rb` and
|
|
186
|
+
`spec/oasis/relaton/oasis/processor_spec.rb` guard both facts.
|
|
187
|
+
|
|
188
|
+
### Key Design Decisions
|
|
189
|
+
|
|
190
|
+
- **The shard path does not fall back.** A shard 404 is "not found" (`[]`), a
|
|
191
|
+
transport failure raises `Relaton::RequestError` so `Db#net_retry` retries it,
|
|
192
|
+
and an unreadable manifest/shard raises `Relaton::Index::Error`. Falling back to
|
|
193
|
+
the monolith would turn a Pages outage into a silent full download.
|
|
194
|
+
`Relaton::RequestError` is declared in `lib/relaton/core/request_error.rb`
|
|
195
|
+
(`Relaton::Core::RequestError` is an alias). `relaton/index` requires that one file,
|
|
196
|
+
not all of `relaton/core` or `relaton/bib`.
|
|
197
|
+
- **`Type#actual?` compares `pages_url`.** Without it `Pool#type` would keep
|
|
198
|
+
serving a zip-backed `Type` to a caller that asks for the shards, or the
|
|
199
|
+
reverse. A spec fixture that seeds the pool must answer `actual?` for
|
|
200
|
+
`pages_url:` as well (see `spec/w3c/support/webmock.rb`).
|
|
201
|
+
- **`Type#search` does not look at a loaded `@index` in shard mode.** A parsed
|
|
202
|
+
query always goes to its shard, so the 24 h TTL applies; `@index`, once
|
|
203
|
+
loaded, is not refreshed.
|
|
204
|
+
- Remote indexes cached for 24 hours at `~/.relaton/{type}/index.yaml`
|
|
205
|
+
- Thread safety via `@@mutex` in FileIO prevents concurrent downloads of the same file
|
|
206
|
+
- Pubid deserialization is optional — when `pubid_class` is provided, string IDs are converted to structured objects
|
|
207
|
+
- Index format validation checks for required `:id` and `:file` keys, with automatic recovery (re-download or removal) on corruption
|