relaton 3.0.7 → 3.0.8

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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/bin/relaton +20 -0
  3. data/lib/relaton/bib/model/docidentifier.rb +17 -2
  4. data/lib/relaton/bibcollection.rb +146 -0
  5. data/lib/relaton/bibdata.rb +92 -0
  6. data/lib/relaton/bipm/data_outcomes_parser.rb +6 -1
  7. data/lib/relaton/cli/base_convertor.rb +134 -0
  8. data/lib/relaton/cli/command.rb +390 -0
  9. data/lib/relaton/cli/data_fetcher.rb +20 -0
  10. data/lib/relaton/cli/frontend/dist/404.html +18 -0
  11. data/lib/relaton/cli/frontend/dist/app.iife.js +1 -0
  12. data/lib/relaton/cli/frontend/dist/style.css +3 -0
  13. data/lib/relaton/cli/frontend/index.html +14 -0
  14. data/lib/relaton/cli/frontend/package-lock.json +2945 -0
  15. data/lib/relaton/cli/frontend/package.json +29 -0
  16. data/lib/relaton/cli/frontend/public/404.html +18 -0
  17. data/lib/relaton/cli/frontend/src/App.test.ts +501 -0
  18. data/lib/relaton/cli/frontend/src/App.vue +506 -0
  19. data/lib/relaton/cli/frontend/src/app.ts +90 -0
  20. data/lib/relaton/cli/frontend/src/components/DocumentDetail.test.ts +142 -0
  21. data/lib/relaton/cli/frontend/src/components/DocumentDetail.vue +228 -0
  22. data/lib/relaton/cli/frontend/src/components/DocumentRow.vue +88 -0
  23. data/lib/relaton/cli/frontend/src/components/Icon.test.ts +44 -0
  24. data/lib/relaton/cli/frontend/src/components/Icon.vue +55 -0
  25. data/lib/relaton/cli/frontend/src/components/Pagination.vue +26 -0
  26. data/lib/relaton/cli/frontend/src/dev/main.ts +100 -0
  27. data/lib/relaton/cli/frontend/src/dev/sample-data.json +71 -0
  28. data/lib/relaton/cli/frontend/src/lib/filter.test.ts +88 -0
  29. data/lib/relaton/cli/frontend/src/lib/filter.ts +95 -0
  30. data/lib/relaton/cli/frontend/src/lib/hydrate.test.ts +102 -0
  31. data/lib/relaton/cli/frontend/src/lib/hydrate.ts +55 -0
  32. data/lib/relaton/cli/frontend/src/lib/shards.test.ts +319 -0
  33. data/lib/relaton/cli/frontend/src/lib/shards.ts +183 -0
  34. data/lib/relaton/cli/frontend/src/lib/types.ts +148 -0
  35. data/lib/relaton/cli/frontend/src/lib/url.test.ts +151 -0
  36. data/lib/relaton/cli/frontend/src/lib/url.ts +111 -0
  37. data/lib/relaton/cli/frontend/src/shims-vue.d.ts +5 -0
  38. data/lib/relaton/cli/frontend/src/styles/index.css +27 -0
  39. data/lib/relaton/cli/frontend/src/test/fetch.ts +58 -0
  40. data/lib/relaton/cli/frontend/tsconfig.json +20 -0
  41. data/lib/relaton/cli/frontend/vite.config.ts +42 -0
  42. data/lib/relaton/cli/frontend/vitest.config.ts +11 -0
  43. data/lib/relaton/cli/frontend_assets.rb +58 -0
  44. data/lib/relaton/cli/full_text_search.rb +98 -0
  45. data/lib/relaton/cli/i18n.yaml +1 -0
  46. data/lib/relaton/cli/index_item_normalizer.rb +380 -0
  47. data/lib/relaton/cli/index_site_generator.rb +1015 -0
  48. data/lib/relaton/cli/relaton_file.rb +290 -0
  49. data/lib/relaton/cli/subcommand_collection.rb +263 -0
  50. data/lib/relaton/cli/subcommand_db.rb +81 -0
  51. data/lib/relaton/cli/templates/_document.liquid +111 -0
  52. data/lib/relaton/cli/templates/_index.liquid +61 -0
  53. data/lib/relaton/cli/templates/index/page.liquid +20 -0
  54. data/lib/relaton/cli/templates/index-style.css +1017 -0
  55. data/lib/relaton/cli/util.rb +8 -0
  56. data/lib/relaton/cli/version.rb +11 -0
  57. data/lib/relaton/cli/xml_convertor.rb +47 -0
  58. data/lib/relaton/cli/xml_to_html_renderer.rb +117 -0
  59. data/lib/relaton/cli/yaml_convertor.rb +102 -0
  60. data/lib/relaton/cli.rb +113 -0
  61. data/lib/relaton/doi/CLAUDE.md +1 -1
  62. data/lib/relaton/element_finder.rb +29 -0
  63. data/lib/relaton/gb/CLAUDE.md +1 -1
  64. data/lib/relaton/ietf/CLAUDE.md +1 -1
  65. data/lib/relaton/iho/CLAUDE.md +3 -3
  66. data/lib/relaton/iho/bibliography.rb +41 -27
  67. data/lib/relaton/iho/processor.rb +3 -2
  68. data/lib/relaton/nist/CLAUDE.md +11 -1
  69. data/lib/relaton/nist/README.adoc +7 -1
  70. data/lib/relaton/nist/hit_collection.rb +25 -1
  71. data/lib/relaton/nist/mods_parser.rb +32 -8
  72. data/lib/relaton/nist/pubs_export.rb +69 -11
  73. data/lib/relaton/ogc/CLAUDE.md +1 -1
  74. data/lib/relaton/omg/CLAUDE.md +1 -1
  75. data/lib/relaton/plateau/CLAUDE.md +1 -1
  76. data/lib/relaton/render/general.rb +319 -0
  77. data/lib/relaton/render/iso690/csl_import.rb +129 -0
  78. data/lib/relaton/render/iso690/element.rb +153 -0
  79. data/lib/relaton/render/iso690/elements/access.rb +44 -0
  80. data/lib/relaton/render/iso690/elements/authorizer.rb +45 -0
  81. data/lib/relaton/render/iso690/elements/citeid.rb +39 -0
  82. data/lib/relaton/render/iso690/elements/component_part.rb +97 -0
  83. data/lib/relaton/render/iso690/elements/creator.rb +267 -0
  84. data/lib/relaton/render/iso690/elements/edition.rb +97 -0
  85. data/lib/relaton/render/iso690/elements/extent.rb +71 -0
  86. data/lib/relaton/render/iso690/elements/identifier.rb +46 -0
  87. data/lib/relaton/render/iso690/elements/location.rb +73 -0
  88. data/lib/relaton/render/iso690/elements/medium.rb +32 -0
  89. data/lib/relaton/render/iso690/elements/numeration.rb +26 -0
  90. data/lib/relaton/render/iso690/elements/production.rb +111 -0
  91. data/lib/relaton/render/iso690/elements/pub_date.rb +63 -0
  92. data/lib/relaton/render/iso690/elements/series.rb +66 -0
  93. data/lib/relaton/render/iso690/elements/size.rb +45 -0
  94. data/lib/relaton/render/iso690/elements/status.rb +30 -0
  95. data/lib/relaton/render/iso690/elements/stddoc.rb +40 -0
  96. data/lib/relaton/render/iso690/elements/title.rb +56 -0
  97. data/lib/relaton/render/iso690/elements/updated.rb +38 -0
  98. data/lib/relaton/render/iso690/elements.rb +67 -0
  99. data/lib/relaton/render/iso690/fields.rb +142 -0
  100. data/lib/relaton/render/iso690/i18n/en.yml +48 -0
  101. data/lib/relaton/render/iso690/i18n/fr.yml +14 -0
  102. data/lib/relaton/render/iso690/i18n/ja.yml +38 -0
  103. data/lib/relaton/render/iso690/i18n.rb +83 -0
  104. data/lib/relaton/render/iso690/identifier_kinds.rb +29 -0
  105. data/lib/relaton/render/iso690/index_renderer.rb +57 -0
  106. data/lib/relaton/render/iso690/kinds.rb +53 -0
  107. data/lib/relaton/render/iso690/renderer.rb +150 -0
  108. data/lib/relaton/render/iso690/rules/bipm.rb +186 -0
  109. data/lib/relaton/render/iso690/rules/jis.rb +103 -0
  110. data/lib/relaton/render/iso690/rules/nist.rb +384 -0
  111. data/lib/relaton/render/iso690/rules.rb +227 -0
  112. data/lib/relaton/render/iso690/style/locale.rb +59 -0
  113. data/lib/relaton/render/iso690/style/name_form.rb +87 -0
  114. data/lib/relaton/render/iso690/style/scheme.rb +33 -0
  115. data/lib/relaton/render/iso690/style/sort_rule.rb +25 -0
  116. data/lib/relaton/render/iso690/style/template_map.rb +40 -0
  117. data/lib/relaton/render/iso690/style/type_template.rb +45 -0
  118. data/lib/relaton/render/iso690/style.rb +227 -0
  119. data/lib/relaton/render/iso690/styles/apa.yml +45 -0
  120. data/lib/relaton/render/iso690/styles/author-date.yml +31 -0
  121. data/lib/relaton/render/iso690/styles/chicago.yml +49 -0
  122. data/lib/relaton/render/iso690/styles/ieee-sa.yml +45 -0
  123. data/lib/relaton/render/iso690/styles/jis-en.yml +68 -0
  124. data/lib/relaton/render/iso690/styles/jis-ja.yml +67 -0
  125. data/lib/relaton/render/iso690/styles/lncs.yml +54 -0
  126. data/lib/relaton/render/iso690/styles/mla.yml +56 -0
  127. data/lib/relaton/render/iso690/template.rb +248 -0
  128. data/lib/relaton/render/iso690.rb +28 -0
  129. data/lib/relaton/render/version.rb +5 -0
  130. data/lib/relaton/un/CLAUDE.md +1 -1
  131. data/lib/relaton/version.rb +1 -1
  132. data/lib/relaton-cli.rb +17 -0
  133. data/lib/relaton-render.rb +13 -0
  134. metadata +192 -21
@@ -0,0 +1,390 @@
1
+ require "date"
2
+ require "tmpdir"
3
+ require "shellwords"
4
+ require "pubid"
5
+ require "relaton/cli/relaton_file"
6
+ require "relaton/cli/xml_convertor"
7
+ require "relaton/cli/yaml_convertor"
8
+ require "relaton/cli/data_fetcher"
9
+ require "relaton/cli/index_site_generator"
10
+ require "relaton/cli/subcommand_collection"
11
+ require "relaton/cli/subcommand_db"
12
+ require "fcntl"
13
+
14
+ module Relaton
15
+ module Cli
16
+ class Command < Thor
17
+ include Relaton::Cli
18
+ class_before :relaton_config
19
+ class_option :verbose, aliases: :v, type: :boolean, desc: "Output warnings"
20
+
21
+ # Exit non-zero when Thor itself rejects an invocation (unknown argument,
22
+ # wrong arity, a raised Thor::Error). Thor's legacy default is to print the
23
+ # error and exit **0**, which in CI reads as success: a caller passing a
24
+ # removed flag would write no site at all and still go green. The Pages
25
+ # deploy in relaton/support runs unattended across ~29 data repos, so a
26
+ # silent green there publishes an empty index nobody notices.
27
+ def self.exit_on_failure?
28
+ true
29
+ end
30
+
31
+ desc "version", "Show Relaton version"
32
+
33
+ def version
34
+ Relaton::Cli.version
35
+ end
36
+
37
+ desc "fetch CODE", "Fetch Relaton XML for Standard identifier CODE"
38
+ option :type, aliases: :t,
39
+ desc: "Type of standard to get bibliographic entry for"
40
+ option :format, aliases: :f,
41
+ desc: "Output format (xml, yaml, bibtex). Default xml."
42
+ option :year, aliases: :y, type: :numeric, desc: "Year the standard was published"
43
+ option :"all-parts", type: :boolean, desc: "Fetch all parts"
44
+ option :"keep-year", type: :boolean,
45
+ desc: "Undated reference should return actual reference with year"
46
+ option :retries, aliases: :r, type: :numeric,
47
+ desc: "Number of network retries. Default 1."
48
+ option :"no-cache", type: :boolean, desc: "Ignore cache"
49
+ option :"publication-date-before",
50
+ desc: "Fetch only documents published before the specified date " \
51
+ "(e.g. 2008, 2008-02, or 2008-02-02)"
52
+ option :"publication-date-after",
53
+ desc: "Fetch only documents published after the specified date " \
54
+ "(e.g. 2002, 2002-01, or 2002-01-01)"
55
+
56
+ def fetch(code)
57
+ io = IO.new($stdout.fcntl(::Fcntl::F_DUPFD), mode: "w:UTF-8")
58
+ io.puts(fetch_document(code, options) || supported_type_message)
59
+ end
60
+
61
+ desc "Metanorma-XML-File / Directory Relaton-XML-Directory",
62
+ "Export Relaton XML from a Metanorma XML file / directory"
63
+ option :extension, aliases: :x, default: "rxl", desc: "File extension of Relaton XML files, " \
64
+ "defaults to 'rxl'"
65
+
66
+ def extract(source_dir, outdir)
67
+ Relaton::Cli::RelatonFile.extract(source_dir, outdir, options)
68
+ end
69
+
70
+ desc "concatenate SOURCE-DIR COLLECTION-FILE",
71
+ "Concatenate entries in DIRECTORY (containing Relaton-XML or YAML) into a Relaton Collection"
72
+ option :title, aliases: :t, desc: "Title of resulting Relaton collection"
73
+ option :organization, aliases: :g, desc: "Organization owner of Relaton collection"
74
+ option :extension, aliases: :x, desc: "File extension of destination " \
75
+ "Relaton file, defaults to 'rxl'"
76
+
77
+ def concatenate(source_dir, outfile)
78
+ Relaton::Cli::RelatonFile.concatenate(source_dir, outfile, options)
79
+ end
80
+
81
+ desc "split Relaton-Collection-File Relaton-XML-Directory",
82
+ "Split a Relaton Collection into multiple files"
83
+ option :extension, aliases: :x, default: "rxl", desc: "File extension of Relaton XML files, " \
84
+ "defaults to 'rxl'"
85
+
86
+ def split(source, outdir)
87
+ Relaton::Cli::RelatonFile.split(source, outdir, options)
88
+ end
89
+
90
+ desc "yaml2xml YAML", "Convert Relaton YAML into Relaton Collection XML " \
91
+ "or separate files"
92
+ option :extension, aliases: :x, default: "rxl", desc: "File extension of Relaton XML files, " \
93
+ "defaults to 'rxl'"
94
+ option :prefix, aliases: :p, desc: "Filename prefix of individual " \
95
+ "Relaton XML files, defaults to empty"
96
+ option :outdir, aliases: :o, desc: "Output to the specified directory " \
97
+ "with individual Relaton Bibdata XML files"
98
+ option :require, aliases: :r, type: :array, desc: "Require LIBRARY " \
99
+ "prior to execution"
100
+ option :overwrite, aliases: :f, type: :boolean, default: false,
101
+ desc: "Overwrite the existing file"
102
+
103
+ def yaml2xml(filename)
104
+ Relaton::Cli::YAMLConvertor.to_xml(filename, options)
105
+ end
106
+
107
+ desc "xml2yaml XML", "Convert Relaton XML into Relaton Bibdata / " \
108
+ "Bibcollection YAML (and separate files)"
109
+ option :extension, aliases: :x, default: "yaml", desc: "File extension of Relaton YAML files, " \
110
+ "defaults to 'yaml'"
111
+ option :prefix, aliases: :p, desc: "Filename prefix of Relaton XML " \
112
+ "files, defaults to empty"
113
+ option :outdir, aliases: :o, desc: "Output to the specified directory " \
114
+ "with individual Relaton Bibdata YAML files"
115
+ option :require, aliases: :r, type: :array, desc: "Require LIBRARY " \
116
+ "prior to execution"
117
+ option :overwrite, aliases: :f, type: :boolean, default: false,
118
+ desc: "Overwrite the existing file"
119
+
120
+ def xml2yaml(filename)
121
+ Relaton::Cli::XMLConvertor.to_yaml(filename, options)
122
+ end
123
+
124
+ desc "xml2html RELATON-INDEX-XML", "Convert Relaton Collection XML into HTML"
125
+ option :stylesheet, aliases: :s, desc: "Stylesheet file path for " \
126
+ "rendering HTML index"
127
+ option :templatedir, aliases: :t, desc: "Liquid template directory for " \
128
+ "rendering Relaton items and collection"
129
+ option :overwrite, aliases: :f, type: :boolean, default: false,
130
+ desc: "Overwrite the existing file"
131
+
132
+ def xml2html(file, style = nil, template = nil)
133
+ Relaton::Cli::XMLConvertor.to_html(file, style, template)
134
+ end
135
+
136
+ desc "yaml2html RELATON-INDEX-YAML", "Concatenate Relaton Collection " \
137
+ "YAML into HTML"
138
+ option :stylesheet, aliases: :s, desc: "Stylesheet file path for " \
139
+ "rendering HTML index"
140
+ option :templatedir, aliases: :t, desc: "Liquid template directory for " \
141
+ "rendering Relaton items and collection"
142
+ option :overwrite, aliases: :f, type: :boolean, default: false,
143
+ desc: "Overwrite the existing file"
144
+
145
+ def yaml2html(file, style = nil, template = nil)
146
+ Relaton::Cli::YAMLConvertor.to_html(file, style, template)
147
+ end
148
+
149
+ desc "index [DATA-DIR]",
150
+ "Build a browsable HTML index site from a folder of Relaton YAML docs (default ./data)"
151
+ option :output, aliases: :o, default: "_site",
152
+ desc: "Output directory for the generated site"
153
+ option :flavor,
154
+ desc: "Build from relaton/relaton-data-<flavor> (shallow-cloned) instead of a local folder"
155
+ option :repo,
156
+ desc: "Build from a GitHub repo ORG/NAME (shallow-cloned) instead of a local folder"
157
+ option :branch, desc: "Branch to clone when --flavor/--repo is used"
158
+ option :title, aliases: :t, desc: "Index page title"
159
+ option :description,
160
+ desc: "Index page description (<meta name=\"description\"> and the header subtitle)"
161
+ option :favicon,
162
+ desc: "Favicon href for <link rel=\"icon\">, used verbatim (a relative " \
163
+ "path must be placed in the deployed site by the caller)"
164
+ option :"base-url",
165
+ desc: "Base URL for raw YAML links (defaults to the raw GitHub URL when cloning)"
166
+ option :"shard-size", type: :numeric, default: 5000,
167
+ desc: "Summary records per search-NNNN.json shard"
168
+ option :"detail-shard-size", type: :numeric, default: 500,
169
+ desc: "Detail records per detail-NNNN.json shard"
170
+ option :detail, type: :boolean, default: true,
171
+ desc: "Emit the detail shards the document panel fetches; --no-detail disables"
172
+ option :static, type: :boolean, default: true,
173
+ desc: "Also index a sibling static/ folder next to DATA-DIR (auto-detected); --no-static disables"
174
+ option :overwrite, aliases: :f, type: :boolean, default: true,
175
+ desc: "Overwrite existing output files"
176
+ option :"publish-data", type: :boolean, default: false,
177
+ desc: "Copy the corpus onto the site so document fetches " \
178
+ "share the Pages origin (for repos whose data is " \
179
+ "served only from there); --no-publish-data disables"
180
+ option :"pubid-flavor",
181
+ desc: "Flavor whose pubid Identifier parses docids and whose INDEXFILE " \
182
+ "names the published index (e.g. iso, iho, iec). Required unless " \
183
+ "--no-machine-index"
184
+ option :"machine-index", type: :boolean, default: true,
185
+ desc: "Emit the machine-consumable index (index/manifest.json, " \
186
+ "index/shard-NNNNN.json and the index-vN monolith); " \
187
+ "--no-machine-index builds the human site only"
188
+ option :"index-name",
189
+ desc: "Base name for the published index, overriding the flavor's " \
190
+ "INDEXFILE (e.g. index-v2). For a corpus that is not a relaton flavor"
191
+
192
+ def index(data_dir = nil)
193
+ with_index_source(data_dir) do |dir, default_base_url, default_title|
194
+ Relaton::Cli::IndexSiteGenerator.generate(
195
+ dir,
196
+ output: options[:output],
197
+ shard_size: options[:"shard-size"].to_i,
198
+ detail_shard_size: options[:"detail-shard-size"].to_i,
199
+ detail: options[:detail],
200
+ title: options[:title] || default_title,
201
+ description: options[:description],
202
+ favicon: options[:favicon],
203
+ base_url: options[:"base-url"] || default_base_url,
204
+ static: options[:static],
205
+ overwrite: options[:overwrite],
206
+ publish_data: options[:"publish-data"],
207
+ flavor: options[:"pubid-flavor"],
208
+ machine_index: options[:"machine-index"],
209
+ index_name: options[:"index-name"],
210
+ )
211
+ end
212
+ end
213
+
214
+ desc "convert XML", "Convert Relaton XML document"
215
+ option :format, aliases: :f, required: true,
216
+ desc: "Output format (yaml, bibtex, asciibib, citation)"
217
+ # Plain-text citation rendering via relaton-render's CitationStyle
218
+ # instances (relaton-cli#94): the style names a Style YAML, the engine
219
+ # stays style-free.
220
+ option :style, desc: "Citation style for --format citation " \
221
+ "(author-date, apa, chicago, mla, ieee-sa, lncs; a Style YAML path " \
222
+ "also works). Default: author-date"
223
+ option :output, aliases: :o, desc: "Output to the specified file"
224
+
225
+ def convert(file) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
226
+ xml = Nokogiri::XML(File.read(file, encoding: "UTF-8"))
227
+ item = Relaton::Cli.parse_xml xml
228
+ result = if /yaml|yml/.match?(options[:format])
229
+ item.to_yaml
230
+ elsif options[:format] == "citation"
231
+ # The paths that exist in every published relaton-render;
232
+ # the gem's conventional "relaton/render" entry is newer.
233
+ require "relaton/render/general"
234
+ require "relaton/render/iso690"
235
+ Relaton::Render::General
236
+ .new(style: options[:style] || "author-date")
237
+ .render(item, embedded: true)
238
+ else item.send "to_#{options[:format]}"
239
+ end
240
+ ext = case options[:format]
241
+ when "bibtex" then "bib"
242
+ when "asciibib" then "adoc"
243
+ when "citation" then "txt"
244
+ else options[:format]
245
+ end
246
+ output = options[:output] || file.sub(/(?<=\.)[^.]+$/, ext)
247
+ File.write output, result, encoding: "UTF-8"
248
+ end
249
+
250
+ desc "fetch-data SOURCE", "Fetch all the documents from a source"
251
+ option :output, aliases: :o, desc: "Output dir. Default: ./data/"
252
+ option :format, aliases: :f, desc: "Output format (yaml, xml, bibxml). Default: yaml"
253
+
254
+ def fetch_data(source)
255
+ DataFetcher.fetch source, options
256
+ end
257
+
258
+ desc "collection SUBCOMMAND", "Collection manipulations"
259
+ subcommand "collection", SubcommandCollection
260
+
261
+ desc "db SUBCOMMAND", "Cache DB manipulation"
262
+ subcommand "db", SubcommandDb
263
+
264
+ no_commands do
265
+ def relaton_config
266
+ Relaton::Logger.configure do |conf|
267
+ if options[:verbose]
268
+ conf.logger_pool[:default].add_level :info
269
+ else
270
+ conf.logger_pool[:default].remove_level :info
271
+ end
272
+ end
273
+ end
274
+
275
+ # Resolve the source folder for `index`. Locally it's just DATA-DIR
276
+ # (default ./data). With --flavor/--repo it shallow-clones the GitHub
277
+ # repo into a temp dir and yields its data/ subfolder plus sensible
278
+ # defaults for the raw-YAML base URL and page title. The temp clone is
279
+ # removed after the block returns.
280
+ def with_index_source(data_dir)
281
+ subdir = data_dir || "data"
282
+ if options[:flavor] || options[:repo]
283
+ repo = options[:repo] || "relaton/relaton-data-#{options[:flavor]}"
284
+ flavor = options[:flavor] ||
285
+ repo.split("/").last.sub(/\Arelaton-data-/, "")
286
+ Dir.mktmpdir("relaton-index-") do |tmp|
287
+ branch = clone_data_repo(repo, options[:branch], tmp)
288
+ base = "https://raw.githubusercontent.com/#{repo}/#{branch}"
289
+ yield File.join(tmp, subdir), base, "#{flavor.upcase} Index"
290
+ end
291
+ else
292
+ yield subdir, nil, nil
293
+ end
294
+ end
295
+
296
+ # Shallow-clone ORG/NAME into `dest`; return the checked-out branch name.
297
+ def clone_data_repo(repo, branch, dest)
298
+ url = "https://github.com/#{repo}.git"
299
+ cmd = ["git", "clone", "--depth", "1", "--single-branch"]
300
+ cmd += ["--branch", branch] if branch
301
+ cmd += [url, dest]
302
+ Relaton::Cli::Util.info "Cloning #{url}…"
303
+ unless system(*cmd, out: File::NULL, err: File::NULL)
304
+ raise Thor::Error,
305
+ "Failed to clone #{url}. Check the repo/branch name and that git is installed."
306
+ end
307
+ branch || `git -C #{Shellwords.escape(dest)} rev-parse --abbrev-ref HEAD`.strip
308
+ end
309
+ end
310
+ end
311
+
312
+ private
313
+
314
+ DATE_FILTER_FORMAT = /\A\d{4}(-\d{2}(-\d{2})?)?\z/
315
+
316
+ def parse_date_option(value, name)
317
+ return unless value
318
+
319
+ unless value.match?(DATE_FILTER_FORMAT)
320
+ raise ArgumentError,
321
+ "Invalid #{name}: #{value.inspect}. Expected YYYY, YYYY-MM, or YYYY-MM-DD."
322
+ end
323
+ parts = value.split("-").map(&:to_i)
324
+ Date.new(*parts.concat([1] * (3 - parts.size)))
325
+ rescue Date::Error
326
+ raise ArgumentError,
327
+ "Invalid #{name}: #{value.inspect}. Date components are out of range."
328
+ end
329
+
330
+ def validate_date_range(date_after, date_before)
331
+ return unless date_after && date_before
332
+ return if date_after < date_before
333
+
334
+ raise ArgumentError,
335
+ "Invalid date range: --publication-date-after (#{date_after}) must be before --publication-date-before (#{date_before})."
336
+ end
337
+
338
+ # @param code [String]
339
+ # @param options [Hash]
340
+ # @option options [String] :type
341
+ # @option options [String, NilClass] :format
342
+ # @option options [Integer, NilClass] :year
343
+ # @return [String, nil]
344
+ def fetch_document(code, options) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/AbcSize,Metrics/MethodLength
345
+ year = options[:year]&.to_s
346
+ dup_opts = options.dup.transform_keys { |k| k.to_s.gsub("-", "_").to_sym }
347
+ %i[publication_date_before publication_date_after].each do |key|
348
+ dup_opts[key] = parse_date_option(dup_opts[key], key.to_s.tr("_", "-").prepend("--")) if dup_opts[key]
349
+ end
350
+ validate_date_range dup_opts[:publication_date_after], dup_opts[:publication_date_before]
351
+ if (processor = Relaton::Db::Registry.instance.by_type options[:type]&.upcase)
352
+ doc = Relaton.db.fetch_std code, year, processor.short, **dup_opts
353
+ elsif options[:type] then return
354
+ else doc = Relaton.db.fetch(code, year, **dup_opts)
355
+ end
356
+ return "No matching bibliographic entry found" unless doc
357
+
358
+ serialize doc, options[:format]
359
+ rescue Relaton::UnknownReferenceError => e
360
+ # No flavor recognizes the reference: log it, return no document (the
361
+ # command then prints the supported types).
362
+ Util.warn e.message
363
+ nil
364
+ rescue Pubid::Errors::Error
365
+ %("#{code}" is not a recognized standards identifier)
366
+ rescue Relaton::RequestError => e
367
+ e.message
368
+ end
369
+
370
+ # @param doc [Relaton::Bib::ItemData]
371
+ # @param format [String]
372
+ # @return [String]
373
+ def serialize(doc, format)
374
+ case format
375
+ when "yaml", "yml" then doc.to_yaml
376
+ when "bibtex" then doc.to_bibtex
377
+ else doc.to_xml bibdata: true
378
+ end
379
+ end
380
+
381
+ def supported_type_message
382
+ ["Recognised types:", registered_types.sort.join(", ")].join(" ")
383
+ end
384
+
385
+ def registered_types
386
+ @registered_types ||=
387
+ Relaton::Db::Registry.instance.processors.each.map { |_n, pr| pr.prefix }
388
+ end
389
+ end
390
+ end
@@ -0,0 +1,20 @@
1
+ module Relaton
2
+ module Cli
3
+ module DataFetcher
4
+ def fetch(source, options)
5
+ processor = Relaton::Db::Registry.instance.find_processor_by_dataset source
6
+ unless processor
7
+ Util.warn "no processor found for `#{source}`"
8
+ return
9
+ end
10
+
11
+ opts = {}
12
+ opts[:output] = options[:output] if options[:output]
13
+ opts[:format] = options[:format] if options[:format]
14
+ processor.fetch_data source, opts
15
+ end
16
+
17
+ extend DataFetcher
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Redirecting…</title>
6
+ <script>
7
+ // GitHub Pages has no URL rewrites, so path-based document URLs
8
+ // (/doc/<id>) land on this 404 fallback. Hand the id to the app as
9
+ // ?doc=<id>, preserving the site's base path wherever it is hosted.
10
+ (function () {
11
+ var m = location.pathname.match(/\/doc\/([^/?#]+)$/);
12
+ var root = m ? location.pathname.slice(0, m.index) : location.pathname.replace(/[^/]*$/, "");
13
+ location.replace(root + "?doc=" + (m ? m[1] : ""));
14
+ })();
15
+ </script>
16
+ </head>
17
+ <body></body>
18
+ </html>