relaton-cli 1.4.0 → 1.7.pre3
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/.github/workflows/rake.yml +46 -0
- data/Gemfile +0 -1
- data/docs/README.adoc +202 -7
- data/lib/relaton/bibcollection.rb +53 -35
- data/lib/relaton/bibdata.rb +3 -1
- data/lib/relaton/cli.rb +71 -32
- data/lib/relaton/cli/base_convertor.rb +1 -1
- data/lib/relaton/cli/command.rb +123 -61
- data/lib/relaton/cli/full_text_search.rb +91 -0
- data/lib/relaton/cli/relaton_file.rb +1 -1
- data/lib/relaton/cli/subcommand_collection.rb +221 -0
- data/lib/relaton/cli/subcommand_db.rb +73 -0
- data/lib/relaton/cli/version.rb +1 -1
- data/lib/relaton/cli/xml_to_html_renderer.rb +14 -7
- data/lib/relaton/cli/yaml_convertor.rb +1 -1
- data/lib/relaton/element_finder.rb +5 -5
- data/relaton-cli.gemspec +7 -6
- data/templates/_index.liquid +9 -1
- metadata +34 -33
- data/.github/workflows/macos.yml +0 -32
- data/.github/workflows/ubuntu.yml +0 -32
- data/.github/workflows/windows.yml +0 -35
data/lib/relaton/cli.rb
CHANGED
@@ -3,53 +3,92 @@ require "relaton"
|
|
3
3
|
require_relative "cli/command"
|
4
4
|
|
5
5
|
module Relaton
|
6
|
-
def self.db
|
7
|
-
Cli.relaton
|
6
|
+
def self.db(dir = nil)
|
7
|
+
Cli.relaton dir
|
8
8
|
end
|
9
9
|
|
10
10
|
module Cli
|
11
11
|
class RelatonDb
|
12
12
|
include Singleton
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
DBCONF = "#{Dir.home}/.relaton/dbpath".freeze
|
15
|
+
|
16
|
+
# @param dir [String, nil]
|
17
|
+
# @return [Relaton::Db]
|
18
|
+
def db(dir)
|
19
|
+
if dir
|
20
|
+
File.write DBCONF, dir, encoding: "UTF-8"
|
21
|
+
@db = Relaton::Db.new dir, nil
|
22
|
+
else
|
23
|
+
@db ||= Relaton::Db.new dbpath, nil
|
24
|
+
end
|
16
25
|
end
|
17
|
-
end
|
18
26
|
|
19
|
-
|
20
|
-
Relaton::Cli::Command.start(arguments)
|
21
|
-
end
|
27
|
+
private
|
22
28
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
def self.relaton
|
31
|
-
RelatonDb.instance.db
|
29
|
+
# @return [String] path to DB
|
30
|
+
def dbpath
|
31
|
+
if File.exist?(DBCONF)
|
32
|
+
File.read(DBCONF, encoding: "UTF-8")
|
33
|
+
else "#{Dir.home}/.relaton/cache"
|
34
|
+
end
|
35
|
+
end
|
32
36
|
end
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
class << self
|
39
|
+
def start(arguments)
|
40
|
+
Relaton::Cli::Command.start(arguments)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Relaton
|
44
|
+
#
|
45
|
+
# Based on current setup, we need to initiate a Db instance to
|
46
|
+
# register all of it's supported processor backends. To make it
|
47
|
+
# easier we have added it as a class method so we can use this
|
48
|
+
# whenever necessary.
|
49
|
+
#
|
50
|
+
# @param dir [String, nil]
|
51
|
+
# @return [Relaton::Db]
|
52
|
+
def relaton(dir)
|
53
|
+
RelatonDb.instance.db dir
|
41
54
|
end
|
42
|
-
end
|
43
55
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
56
|
+
# @param content [Nokogiri::XML::Document]
|
57
|
+
# @return [RelatonBib::BibliographicItem,
|
58
|
+
# RelatonIsoBib::IsoBibliongraphicItem]
|
59
|
+
def parse_xml(doc)
|
60
|
+
if (proc = Cli.processor(doc))
|
61
|
+
proc.from_xml(doc.to_s)
|
62
|
+
else
|
63
|
+
RelatonBib::XMLParser.from_xml(doc.to_s)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# @param doc [Nokogiri::XML::Element]
|
68
|
+
# @return [RelatonIso::Processor, RelatonIec::Processor,
|
69
|
+
# RelatonNist::Processor, RelatonIetf::Processot,
|
70
|
+
# RelatonItu::Processor, RelatonGb::Processor,
|
71
|
+
# RelatonOgc::Processor, RelatonCalconnect::Processor]
|
72
|
+
def processor(doc)
|
73
|
+
docid = doc.at "docidentifier"
|
74
|
+
proc = get_proc docid
|
50
75
|
return proc if proc
|
76
|
+
|
77
|
+
Relaton::Registry.instance.by_type(docid&.text&.match(/^\w+/)&.to_s)
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# @param doc [Nokogiri::XML::Element]
|
83
|
+
# @return [RelatonIso::Processor, RelatonIec::Processor,
|
84
|
+
# RelatonNist::Processor, RelatonIetf::Processot,
|
85
|
+
# RelatonItu::Processor, RelatonGb::Processor,
|
86
|
+
# RelatonOgc::Processor, RelatonCalconnect::Processor]
|
87
|
+
def get_proc(docid)
|
88
|
+
return unless docid && docid[:type]
|
89
|
+
|
90
|
+
Relaton::Registry.instance.by_type(docid[:type])
|
51
91
|
end
|
52
|
-
Relaton::Registry.instance.by_type(docid&.text&.match(/^\w+/)&.to_s)
|
53
92
|
end
|
54
93
|
end
|
55
94
|
end
|
data/lib/relaton/cli/command.rb
CHANGED
@@ -1,93 +1,136 @@
|
|
1
1
|
require "relaton/cli/relaton_file"
|
2
2
|
require "relaton/cli/xml_convertor"
|
3
3
|
require "relaton/cli/yaml_convertor"
|
4
|
+
require "relaton/cli/subcommand_collection"
|
5
|
+
require "relaton/cli/subcommand_db"
|
4
6
|
require "fcntl"
|
5
7
|
|
6
8
|
module Relaton
|
7
9
|
module Cli
|
8
10
|
class Command < Thor
|
11
|
+
include Relaton::Cli
|
12
|
+
|
9
13
|
desc "fetch CODE", "Fetch Relaton XML for Standard identifier CODE"
|
10
|
-
option :type, aliases: :t,
|
11
|
-
|
12
|
-
option :
|
14
|
+
option :type, aliases: :t, desc: "Type of standard to "\
|
15
|
+
"get bibliographic entry for"
|
16
|
+
option :format, aliases: :f, desc: "Output format (xml, yaml, bibtex). "\
|
17
|
+
"Default xml."
|
18
|
+
option :year, aliases: :y, type: :numeric, desc: "Year the standard was "\
|
19
|
+
"published"
|
20
|
+
option :"all-parts", type: :boolean, desc: "Fetch all parts"
|
21
|
+
option :"keep-year", type: :boolean, desc: "Undated reference should "\
|
22
|
+
"return actual reference with year"
|
23
|
+
option :retries, aliases: :r, type: :numeric, desc: "Number of network "\
|
24
|
+
"retries. Default 1."
|
13
25
|
|
14
26
|
def fetch(code)
|
15
|
-
Relaton.db
|
16
|
-
io = IO.new(STDOUT.fcntl(::Fcntl::F_DUPFD), mode:
|
27
|
+
# Relaton.db
|
28
|
+
io = IO.new(STDOUT.fcntl(::Fcntl::F_DUPFD), mode: "w:UTF-8")
|
17
29
|
io.puts(fetch_document(code, options) || supported_type_message)
|
18
30
|
end
|
19
31
|
|
20
|
-
desc "extract Metanorma-XML-File / Directory Relaton-XML-Directory",
|
21
|
-
|
32
|
+
desc "extract Metanorma-XML-File / Directory Relaton-XML-Directory",
|
33
|
+
"Extract Relaton XML from Metanorma XML file / directory"
|
34
|
+
option :extension, aliases: :x, default: "rxl", desc: "File extension "\
|
35
|
+
"of Relaton XML files, defaults to 'rxl'"
|
22
36
|
|
23
37
|
def extract(source_dir, outdir)
|
24
38
|
Relaton::Cli::RelatonFile.extract(source_dir, outdir, options)
|
25
39
|
end
|
26
40
|
|
27
|
-
desc "concatenate SOURCE-DIR COLLECTION-FILE", "Concatenate entries in
|
41
|
+
desc "concatenate SOURCE-DIR COLLECTION-FILE", "Concatenate entries in "\
|
42
|
+
"DIRECTORY (containing Relaton-XML or YAML) into a Relaton Collection"
|
28
43
|
option :title, aliases: :t, desc: "Title of resulting Relaton collection"
|
29
|
-
option :organization, aliases: :g, desc: "Organization owner of Relaton
|
30
|
-
|
31
|
-
option :
|
44
|
+
option :organization, aliases: :g, desc: "Organization owner of Relaton "\
|
45
|
+
"collection"
|
46
|
+
# option :new, aliases: :n, type: :boolean, desc: "Use the new Relaton "\
|
47
|
+
# "YAML format"
|
48
|
+
option :extension, aliases: :x, desc: "File extension of destination "\
|
49
|
+
"Relaton file, defaults to 'rxl'"
|
32
50
|
|
33
51
|
def concatenate(source_dir, outfile)
|
34
52
|
Relaton::Cli::RelatonFile.concatenate(source_dir, outfile, options)
|
35
53
|
end
|
36
54
|
|
37
|
-
desc "split Relaton-Collection-File Relaton-XML-Directory", "Split a
|
38
|
-
|
39
|
-
option :
|
55
|
+
desc "split Relaton-Collection-File Relaton-XML-Directory", "Split a "\
|
56
|
+
"Relaton Collection into multiple files"
|
57
|
+
option :extension, aliases: :x, default: "rxl", desc: "File extension "\
|
58
|
+
"of Relaton XML files, defaults to 'rxl'"
|
59
|
+
# option :new, aliases: :n, type: :boolean, desc: "Use the new Relaton "\
|
60
|
+
# "YAML format"
|
40
61
|
|
41
62
|
def split(source, outdir)
|
42
63
|
Relaton::Cli::RelatonFile.split(source, outdir, options)
|
43
64
|
end
|
44
65
|
|
45
|
-
desc "yaml2xml YAML", "Convert Relaton YAML into Relaton Collection XML
|
46
|
-
|
47
|
-
option :
|
48
|
-
|
49
|
-
option :
|
50
|
-
|
66
|
+
desc "yaml2xml YAML", "Convert Relaton YAML into Relaton Collection XML "\
|
67
|
+
"or separate files"
|
68
|
+
option :extension, aliases: :x, default: "rxl", desc: "File extension "\
|
69
|
+
"of Relaton XML files, defaults to 'rxl'"
|
70
|
+
option :prefix, aliases: :p, desc: "Filename prefix of individual "\
|
71
|
+
"Relaton XML files, defaults to empty"
|
72
|
+
option :outdir, aliases: :o, desc: "Output to the specified directory "\
|
73
|
+
"with individual Relaton Bibdata XML files"
|
74
|
+
option :require, aliases: :r, type: :array, desc: "Require LIBRARY "\
|
75
|
+
"prior to execution"
|
76
|
+
option :overwrite, aliases: :f, type: :boolean, default: false,
|
77
|
+
desc: "Overwrite the existing file"
|
51
78
|
|
52
79
|
def yaml2xml(filename)
|
53
80
|
Relaton::Cli::YAMLConvertor.to_xml(filename, options)
|
54
81
|
end
|
55
82
|
|
56
|
-
desc "xml2yaml XML", "Convert Relaton XML into Relaton Bibdata /
|
57
|
-
|
58
|
-
option :
|
59
|
-
|
60
|
-
option :
|
61
|
-
|
83
|
+
desc "xml2yaml XML", "Convert Relaton XML into Relaton Bibdata / "\
|
84
|
+
"Bibcollection YAML (and separate files)"
|
85
|
+
option :extension, aliases: :x, default: "yaml", desc: "File extension "\
|
86
|
+
"of Relaton YAML files, defaults to 'yaml'"
|
87
|
+
option :prefix, aliases: :p, desc: "Filename prefix of Relaton XML "\
|
88
|
+
"files, defaults to empty"
|
89
|
+
option :outdir, aliases: :o, desc: "Output to the specified directory "\
|
90
|
+
"with individual Relaton Bibdata YAML files"
|
91
|
+
option :require, aliases: :r, type: :array, desc: "Require LIBRARY "\
|
92
|
+
"prior to execution"
|
93
|
+
option :overwrite, aliases: :f, type: :boolean, default: false,
|
94
|
+
desc: "Overwrite the existing file"
|
62
95
|
|
63
96
|
def xml2yaml(filename)
|
64
97
|
Relaton::Cli::XMLConvertor.to_yaml(filename, options)
|
65
98
|
end
|
66
99
|
|
67
|
-
desc "xml2html RELATON-INDEX-XML", "Convert Relaton Collection XML into
|
68
|
-
|
69
|
-
option :
|
70
|
-
|
100
|
+
desc "xml2html RELATON-INDEX-XML", "Convert Relaton Collection XML into "\
|
101
|
+
"HTML"
|
102
|
+
option :stylesheet, aliases: :s, desc: "Stylesheet file path for "\
|
103
|
+
"rendering HTML index"
|
104
|
+
option :templatedir, aliases: :t, desc: "Liquid template directory for "\
|
105
|
+
"rendering Relaton items and collection"
|
106
|
+
option :overwrite, aliases: :f, type: :boolean, default: false,
|
107
|
+
desc: "Overwrite the existing file"
|
71
108
|
|
72
109
|
def xml2html(file, style = nil, template = nil)
|
73
110
|
Relaton::Cli::XMLConvertor.to_html(file, style, template)
|
74
111
|
end
|
75
112
|
|
76
|
-
desc "yaml2html RELATON-INDEX-YAML", "Concatenate Relaton Collection
|
77
|
-
|
78
|
-
option :
|
79
|
-
|
113
|
+
desc "yaml2html RELATON-INDEX-YAML", "Concatenate Relaton Collection "\
|
114
|
+
"YAML into HTML"
|
115
|
+
option :stylesheet, aliases: :s, desc: "Stylesheet file path for "\
|
116
|
+
"rendering HTML index"
|
117
|
+
option :templatedir, aliases: :t, desc: "Liquid template directory for "\
|
118
|
+
"rendering Relaton items and collection"
|
119
|
+
option :overwrite, aliases: :f, type: :boolean, default: false,
|
120
|
+
desc: "Overwrite the existing file"
|
80
121
|
|
81
122
|
def yaml2html(file, style = nil, template = nil)
|
82
123
|
Relaton::Cli::YAMLConvertor.to_html(file, style, template)
|
83
124
|
end
|
84
125
|
|
85
126
|
desc "convert XML", "Convert Relaton XML document"
|
86
|
-
option :format, aliases: :f, required: true, desc: "Output format
|
127
|
+
option :format, aliases: :f, required: true, desc: "Output format "\
|
128
|
+
"(yaml, bibtex, asciibib)"
|
87
129
|
option :output, aliases: :o, desc: "Output to the specified file"
|
88
130
|
|
89
|
-
def convert(file)
|
90
|
-
|
131
|
+
def convert(file) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
132
|
+
xml = Nokogiri::XML(File.read(file, encoding: "UTF-8"))
|
133
|
+
item = Relaton::Cli.parse_xml xml
|
91
134
|
result = if /yaml|yml/.match?(options[:format])
|
92
135
|
item.to_hash.to_yaml
|
93
136
|
else item.send "to_#{options[:format]}"
|
@@ -101,34 +144,53 @@ module Relaton
|
|
101
144
|
File.write output, result, encoding: "UTF-8"
|
102
145
|
end
|
103
146
|
|
104
|
-
|
105
|
-
|
106
|
-
# @param code [String]
|
107
|
-
# @param options [Hash]
|
108
|
-
# @option options [String] :type
|
109
|
-
# @option options [String, NilClass] :format
|
110
|
-
# @option options [Integer, NilClass] :year
|
111
|
-
def fetch_document(code, options)
|
112
|
-
if registered_types.include?(options[:type])
|
113
|
-
doc = Cli.relaton.fetch(code, options[:year]&.to_s)
|
114
|
-
if doc
|
115
|
-
options[:format] == "bibtex" ? doc.to_bibtex : doc.to_xml
|
116
|
-
else
|
117
|
-
"No matching bibliographic entry found"
|
118
|
-
end
|
119
|
-
end
|
120
|
-
rescue RelatonBib::RequestError => e
|
121
|
-
e.message
|
122
|
-
end
|
147
|
+
desc "collection SUBCOMMAND", "Collection manipulations"
|
148
|
+
subcommand "collection", SubcommandCollection
|
123
149
|
|
124
|
-
|
125
|
-
|
150
|
+
desc "db SUBCOMMAND", "Cache DB manipulation"
|
151
|
+
subcommand "db", SubcommandDb
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# @param code [String]
|
157
|
+
# @param options [Hash]
|
158
|
+
# @option options [String] :type
|
159
|
+
# @option options [String, NilClass] :format
|
160
|
+
# @option options [Integer, NilClass] :year
|
161
|
+
# @return [String, nil]
|
162
|
+
def fetch_document(code, options) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity,Metrics/AbcSize
|
163
|
+
year = options[:year]&.to_s
|
164
|
+
if (processor = Relaton::Registry.instance.by_type options[:type]&.upcase)
|
165
|
+
doc = Relaton.db.fetch_std code, year, processor.short, options.dup
|
166
|
+
elsif options[:type] then return
|
167
|
+
else doc = Relaton.db.fetch(code, year, options.dup)
|
126
168
|
end
|
169
|
+
return "No matching bibliographic entry found" unless doc
|
170
|
+
|
171
|
+
serialize doc, options[:format]
|
172
|
+
rescue RelatonBib::RequestError => e
|
173
|
+
e.message
|
174
|
+
end
|
127
175
|
|
128
|
-
|
129
|
-
|
130
|
-
|
176
|
+
# @param doc [RelatonBib::BibliographicItem]
|
177
|
+
# @param format [String]
|
178
|
+
# @return [String]
|
179
|
+
def serialize(doc, format)
|
180
|
+
case format
|
181
|
+
when "yaml", "yml" then doc.to_hash.to_yaml
|
182
|
+
when "bibtex" then doc.to_bibtex
|
183
|
+
else doc.to_xml
|
131
184
|
end
|
132
185
|
end
|
186
|
+
|
187
|
+
def supported_type_message
|
188
|
+
["Recognised types:", registered_types.sort.join(", ")].join(" ")
|
189
|
+
end
|
190
|
+
|
191
|
+
def registered_types
|
192
|
+
@registered_types ||=
|
193
|
+
Relaton::Registry.instance.processors.each.map { |_n, pr| pr.prefix }
|
194
|
+
end
|
133
195
|
end
|
134
196
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# require "forwardable"
|
2
|
+
|
3
|
+
module Relaton
|
4
|
+
class FullTextSeatch
|
5
|
+
# extend Forwardable
|
6
|
+
|
7
|
+
# def_delegators :@collections, :<<
|
8
|
+
|
9
|
+
# @return Regexp
|
10
|
+
attr_reader :regex
|
11
|
+
|
12
|
+
# @param collection [Relaton::Bibcollection]
|
13
|
+
def initialize(collection)
|
14
|
+
@collection = collection
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param text [String]
|
18
|
+
# @return [Array<Hash>]
|
19
|
+
def search(text)
|
20
|
+
@regex = %{(.*?)(.{0,20})(#{text})(.{0,20})(.*)}
|
21
|
+
@matches = @collection.items.reduce({}) do |m, item|
|
22
|
+
# m + results(col, rgx)
|
23
|
+
res = result item
|
24
|
+
m[item.id] = res if res.any?
|
25
|
+
m
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def print_results
|
30
|
+
@matches.each do |docid, attrs|
|
31
|
+
puts " Document ID: #{docid}"
|
32
|
+
print_attrs attrs, 4
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Boolean]
|
37
|
+
def any?
|
38
|
+
@matches.any?
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
44
|
+
def print_attrs(attrs, indent)
|
45
|
+
ind = " " * indent
|
46
|
+
if attrs.is_a? String then puts ind + attrs
|
47
|
+
elsif attrs.is_a? Hash
|
48
|
+
attrs.each do |key, val|
|
49
|
+
pref = "#{ind}#{key}:"
|
50
|
+
if val.is_a? String then puts pref + " " + val
|
51
|
+
else
|
52
|
+
puts pref
|
53
|
+
print_attrs val, indent + 2
|
54
|
+
end
|
55
|
+
end
|
56
|
+
elsif attrs.is_a? Array then attrs.each { |v| print_attrs v, indent + 2 }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param item [Relaton::Bibdata]
|
61
|
+
# @return [Hash]
|
62
|
+
def result(item)
|
63
|
+
if item.is_a? String
|
64
|
+
message $~ if item.match regex
|
65
|
+
elsif item.respond_to? :reduce
|
66
|
+
item.reduce([]) do |m, i|
|
67
|
+
res = result i
|
68
|
+
m << res if res && !res.empty?
|
69
|
+
m
|
70
|
+
end
|
71
|
+
else
|
72
|
+
item.instance_variables.reduce({}) do |m, var|
|
73
|
+
res = result item.instance_variable_get(var)
|
74
|
+
m[var.to_s.tr(":@", "")] = res if res && !res.empty?
|
75
|
+
m
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
# rubocop:enable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
80
|
+
|
81
|
+
# @param match [MatchData]
|
82
|
+
# @return [String]
|
83
|
+
def message(match)
|
84
|
+
msg = ""
|
85
|
+
msg += "..." unless match[1].empty?
|
86
|
+
msg += "#{match[2]}\e[4m#{match[3]}\e[24m#{match[4]}"
|
87
|
+
msg += "..." unless match[5].empty?
|
88
|
+
msg
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|