relaton-cli 1.4.0 → 1.7.pre3
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,221 @@
|
|
1
|
+
require "relaton/cli/full_text_search"
|
2
|
+
|
3
|
+
module Relaton
|
4
|
+
module Cli
|
5
|
+
class SubcommandCollection < Thor
|
6
|
+
desc "create COLLECTION", "Create collection"
|
7
|
+
option :dir, aliases: :d, desc: "Directory to store collection. Default "\
|
8
|
+
"is $HOME/.relaton/collections."
|
9
|
+
option :author, desc: "Author"
|
10
|
+
option :title, desc: "Title"
|
11
|
+
option :doctype, desc: "Documents type"
|
12
|
+
|
13
|
+
def create(file)
|
14
|
+
dir = directory
|
15
|
+
file_path = File.join dir, file
|
16
|
+
col = Relaton::Bibcollection.new options
|
17
|
+
if File.exist? file_path
|
18
|
+
warn "Collection #{file} aready exist"
|
19
|
+
else
|
20
|
+
Dir.mkdir dir unless Dir.exist? dir
|
21
|
+
File.write file_path, col.to_yaml, encoding: "UTF-8"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "info COLLECTION", "View collection information"
|
26
|
+
option :dir, aliases: :d, desc: "Directory to store collection. Default "\
|
27
|
+
"is $HOME/.relaton/collections."
|
28
|
+
|
29
|
+
def info(file) # rubocop:disable Metrics/AbcSize
|
30
|
+
path = File.join directory, file
|
31
|
+
puts "Collection: #{File.basename path}"
|
32
|
+
puts "Last updated: #{File.mtime path}"
|
33
|
+
puts "File size: #{File.size path}"
|
34
|
+
col = Relaton::Bibcollection.new YAML.load_file(path)["root"]
|
35
|
+
puts "Number of items: #{col.items.size}"
|
36
|
+
puts "Author: #{col.author}"
|
37
|
+
puts "Title: #{col.title}"
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "list", "List collections"
|
41
|
+
option :dir, aliases: :d, desc: "Directory with collections. Default is "\
|
42
|
+
"$HOME/.relaton/collections."
|
43
|
+
option :entries, aliases: :e, type: :boolean, desc: "Show entries"
|
44
|
+
|
45
|
+
def list
|
46
|
+
Dir[File.join(directory, "*")].each do |f|
|
47
|
+
yml = read_yaml f
|
48
|
+
if yml && yml["root"]
|
49
|
+
puts File.basename f
|
50
|
+
puts_entries yml
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
map ls: :list
|
56
|
+
|
57
|
+
desc "get CODE", "Fetch document from collection by ID"
|
58
|
+
option :collection, aliases: :c, desc: "Collection to fetch document. "\
|
59
|
+
"By default fetch the first match across all collections."
|
60
|
+
option :dir, aliases: :d, desc: "Directory with collections. Default is "\
|
61
|
+
"$HOME/.relaton/collections."
|
62
|
+
option :format, aliases: :f, desc: "Output format (xml, abb). "\
|
63
|
+
"If not defined the output in a human-readable form."
|
64
|
+
option :output, aliases: :o, desc: "Output to the specified file. The "\
|
65
|
+
" file's extension (abb, xml) defines output format."
|
66
|
+
|
67
|
+
def get(docid)
|
68
|
+
collections.each do |col|
|
69
|
+
col[:collection].items.each do |item|
|
70
|
+
if item.docidentifier == docid
|
71
|
+
output_item(item)
|
72
|
+
return
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
desc "find TEXT", "Full-text search"
|
79
|
+
option :collection, aliases: :c, desc: "Collection to search text. "\
|
80
|
+
"By default search across all collections."
|
81
|
+
option :dir, aliases: :d, desc: "Directory with collections. Default is "\
|
82
|
+
"$HOME/.relaton/collections."
|
83
|
+
|
84
|
+
def find(text)
|
85
|
+
collections.each do |col|
|
86
|
+
searcher = Relaton::FullTextSeatch.new(col[:collection])
|
87
|
+
searcher.search text
|
88
|
+
if searcher.any?
|
89
|
+
puts "Collection: #{File.basename(col[:file])}"
|
90
|
+
searcher.print_results
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
map search: :find
|
96
|
+
|
97
|
+
desc "fetch CODE", "Fetch a document and store it into a collection"
|
98
|
+
option :type, aliases: :t, required: true, desc: "Type of standard to "\
|
99
|
+
"get bibliographic entry for"
|
100
|
+
option :year, aliases: :y, type: :numeric, desc: "Year the standard was "\
|
101
|
+
"published"
|
102
|
+
option :collection, aliases: :c, required: true, desc: "Collection "\
|
103
|
+
"to store a document"
|
104
|
+
option :dir, aliases: :d, desc: "Directory with collections. Default is "\
|
105
|
+
"$HOME/.relaton/collections."
|
106
|
+
|
107
|
+
def fetch(code)
|
108
|
+
doc = Relaton.db.fetch(code, options[:year]&.to_s)
|
109
|
+
if doc
|
110
|
+
colfile = File.join directory, options[:collection]
|
111
|
+
coll = read_collection colfile
|
112
|
+
coll << doc
|
113
|
+
File.write colfile, coll.to_yaml, encoding: "UTF-8"
|
114
|
+
else warn "No matching bibliographic entry found"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
desc "import FILE", "Import document or collection from an XML file "\
|
119
|
+
"into another collection"
|
120
|
+
option :collection, aliases: :c, required: true, desc: "Collection "\
|
121
|
+
"to store a document. If collection doesn't exist then it'll be created."
|
122
|
+
option :dir, aliases: :d, desc: "Directory with collections. Default is "\
|
123
|
+
"$HOME/.relaton/collections."
|
124
|
+
|
125
|
+
def import(file) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
126
|
+
collfile = File.join directory, options[:collection]
|
127
|
+
coll = read_collection collfile
|
128
|
+
xml = Nokogiri::XML File.read(file, encoding: "UTF-8")
|
129
|
+
if xml.at "relaton-collection"
|
130
|
+
if coll
|
131
|
+
coll << Relaton::Bibcollection.from_xml(xml)
|
132
|
+
else
|
133
|
+
coll = Relaton::Bibcollection.from_xml(xml)
|
134
|
+
end
|
135
|
+
else
|
136
|
+
coll ||= Relaton::Bibcollection.new({})
|
137
|
+
coll << Relaton::Bibdata.from_xml(xml)
|
138
|
+
end
|
139
|
+
File.write collfile, coll.to_yaml, encoding: "UTF-8"
|
140
|
+
end
|
141
|
+
|
142
|
+
desc "export COLLECTION", "Export collection into XML file"
|
143
|
+
option :dir, aliases: :d, desc: "Directory with collections. Default is "\
|
144
|
+
"$HOME/.relaton/collections."
|
145
|
+
|
146
|
+
def export(file)
|
147
|
+
coll = read_collection File.join(directory, file)
|
148
|
+
outfile = file.sub(/\.\w+$/, "") + ".xml"
|
149
|
+
File.write outfile, coll.to_xml(bibdata: true), encoding: "UTF-8"
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
# @return [String]
|
155
|
+
def directory
|
156
|
+
options.fetch :dir, File.join(Dir.home, ".relaton/collections")
|
157
|
+
end
|
158
|
+
|
159
|
+
# @param file [String]
|
160
|
+
# @return [Hash]
|
161
|
+
def read_yaml(file)
|
162
|
+
YAML.load_file file if File.file? file
|
163
|
+
rescue Psych::SyntaxError
|
164
|
+
warn "[relaton-cli] WARNING: the file #{file} isn't a collection."
|
165
|
+
end
|
166
|
+
|
167
|
+
# @param file [String]
|
168
|
+
# @return [Relaton::Bibcollection, nil]
|
169
|
+
def read_collection(file)
|
170
|
+
return unless File.file?(file)
|
171
|
+
|
172
|
+
Relaton::Bibcollection.new YAML.load_file(file)["root"]
|
173
|
+
end
|
174
|
+
|
175
|
+
# @return [Array<Hash>]
|
176
|
+
def collections
|
177
|
+
file = options.fetch :collection, "*"
|
178
|
+
Dir[File.join directory, file].reduce([]) do |m, f|
|
179
|
+
yml = read_yaml f
|
180
|
+
if yml && yml["root"]
|
181
|
+
m << { collection: Relaton::Bibcollection.new(yml["root"]),
|
182
|
+
file: f }
|
183
|
+
end
|
184
|
+
m
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Puts document IDs for each item in tthe cokllection
|
189
|
+
# @param hash [Hash] Relaton collection
|
190
|
+
def puts_entries(hash)
|
191
|
+
return unless options[:entries]
|
192
|
+
|
193
|
+
Relaton::Bibcollection.new(hash["root"]).items.each do |b|
|
194
|
+
puts " " + b.docidentifier
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# @param item [Relaton::Bibdata]
|
199
|
+
def output_item(item)
|
200
|
+
case options[:format]
|
201
|
+
when "xml" then puts item.to_xml bibdata: true
|
202
|
+
when "abb" then puts item.to_asciibib
|
203
|
+
else puts_human_readable_item item
|
204
|
+
end
|
205
|
+
out = case options[:output]
|
206
|
+
when /\.abb$/ then item.to_asciibib
|
207
|
+
when /\.xml$/ then item.to_xml bibitem: true
|
208
|
+
end
|
209
|
+
File.write options[:output], out, encoding: "UTF-8" if out
|
210
|
+
end
|
211
|
+
|
212
|
+
# @param item [Relaton::Bibdata]
|
213
|
+
def puts_human_readable_item(item) # rubocop:disable Metrics/AbcSize
|
214
|
+
puts "Document identifier: #{item.docidentifier}"
|
215
|
+
puts "Title: #{item.title.first.title.content}"
|
216
|
+
puts "Status: #{item.status.stage}"
|
217
|
+
item.date.each { |d| puts "Date #{d.type}: #{d.on || d.from}" }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Relaton
|
2
|
+
module Cli
|
3
|
+
class SubcommandDb < Thor
|
4
|
+
include Relaton::Cli
|
5
|
+
|
6
|
+
desc "create DIR", "Create new cache DB. Default DIR is "\
|
7
|
+
"/home/user/.relaon/cache/"
|
8
|
+
|
9
|
+
def create(dir = nil)
|
10
|
+
db = Relaton.db (dir && File.expand_path(dir))
|
11
|
+
path = db.instance_variable_get(:@db).dir
|
12
|
+
warn "Cache DB is in \"#{path}\""
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "mv DIR", "Move cache DB to a new directory"
|
16
|
+
|
17
|
+
def mv(dir)
|
18
|
+
new_path = File.expand_path dir
|
19
|
+
path = Relaton.db.mv new_path
|
20
|
+
if path
|
21
|
+
File.write Cli::RelatonDb::DBCONF, path, encoding: "UTF-8"
|
22
|
+
warn "Cache DB is moved to \"#{path}\""
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc "clear", "Clear cache DB"
|
27
|
+
|
28
|
+
def clear
|
29
|
+
Relaton.db.clear
|
30
|
+
warn "Cache DB is cleared"
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "fetch CODE", "Fetch Relaton XML for Standard identifier CODE from "\
|
34
|
+
"cache DB"
|
35
|
+
option :type, aliases: :t, desc: "Type of standard to "\
|
36
|
+
"get bibliographic entry for"
|
37
|
+
option :format, aliases: :f, desc: "Output format (xml, yaml, bibtex). "\
|
38
|
+
"Default xml."
|
39
|
+
option :year, aliases: :y, type: :numeric, desc: "Year the standard was "\
|
40
|
+
"published"
|
41
|
+
|
42
|
+
def fetch(code)
|
43
|
+
io = IO.new($stdout.fcntl(::Fcntl::F_DUPFD), mode: "w:UTF-8")
|
44
|
+
opts = options.merge(fetch_db: true)
|
45
|
+
io.puts(fetch_document(code, opts) || supported_type_message)
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "fetch_all TEXT", "Query for all documents in a cache DB for a "\
|
49
|
+
"certain string"
|
50
|
+
option :edition, aliases: :e, desc: "Filter entries by edition"
|
51
|
+
option :year, aliases: :y, desc: "Filter entries by year"
|
52
|
+
option :format, aliases: :f, desc: "Output format (xml, yaml, bibtex). "\
|
53
|
+
"Default xml."
|
54
|
+
|
55
|
+
def fetch_all(text = nil) # rubocop:disable Metrics/AbcSize
|
56
|
+
io = IO.new($stdout.fcntl(::Fcntl::F_DUPFD), mode: "w:UTF-8")
|
57
|
+
opts = options.each_with_object({}) do |(k, v), o|
|
58
|
+
o[k.to_sym] = v unless k == "format"
|
59
|
+
end
|
60
|
+
Relaton.db.fetch_all(text, **opts).each do |doc|
|
61
|
+
io.puts serialize(doc, options[:format])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "doctype REF", "Detect document type from REF"
|
66
|
+
|
67
|
+
def doctype(ref)
|
68
|
+
io = IO.new($stdout.fcntl(::Fcntl::F_DUPFD), mode: "w:UTF-8")
|
69
|
+
io.puts Relaton.db.docid_type(ref)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/relaton/cli/version.rb
CHANGED
@@ -13,9 +13,9 @@ module Relaton::Cli
|
|
13
13
|
# @param index_xml [String] Relaton XML
|
14
14
|
# @return [String] HTML
|
15
15
|
def render(index_xml)
|
16
|
-
Liquid::Template
|
17
|
-
parse(template)
|
18
|
-
render(build_liquid_document(index_xml))
|
16
|
+
Liquid::Template
|
17
|
+
.parse(template)
|
18
|
+
.render(build_liquid_document(index_xml))
|
19
19
|
end
|
20
20
|
|
21
21
|
def uri_for_extension(uri, extension)
|
@@ -32,7 +32,7 @@ module Relaton::Cli
|
|
32
32
|
# @param options [Hash]
|
33
33
|
# @return [String] HTML
|
34
34
|
def self.render(file, options)
|
35
|
-
new(options).render(file)
|
35
|
+
new(**options).render(file)
|
36
36
|
end
|
37
37
|
|
38
38
|
private
|
@@ -43,16 +43,23 @@ module Relaton::Cli
|
|
43
43
|
File.read(file, encoding: "utf-8")
|
44
44
|
end
|
45
45
|
|
46
|
+
# rubocop:disable Metrics/MethodLength
|
46
47
|
# @param source [String] Relaton XML
|
47
48
|
def build_liquid_document(source)
|
48
49
|
bibcollection = build_bibcollection(source)
|
49
|
-
|
50
|
+
begin
|
51
|
+
mnv = `metanorma -v`
|
52
|
+
rescue Errno::ENOENT
|
53
|
+
mnv = ""
|
54
|
+
end
|
50
55
|
hash_to_liquid(
|
51
56
|
depth: 2,
|
52
57
|
css: stylesheet,
|
53
58
|
title: bibcollection.title,
|
59
|
+
date: Date.today.to_s,
|
60
|
+
metanorma_v: mnv.lines.first&.strip,
|
54
61
|
author: bibcollection.author,
|
55
|
-
documents: document_items(bibcollection)
|
62
|
+
documents: document_items(bibcollection)
|
56
63
|
)
|
57
64
|
end
|
58
65
|
|
@@ -63,7 +70,7 @@ module Relaton::Cli
|
|
63
70
|
Liquid::Template.file_system = file_system
|
64
71
|
end
|
65
72
|
|
66
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/
|
73
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
67
74
|
|
68
75
|
# TODO: This should be recursive, but it's not
|
69
76
|
# @param hash [Hash]
|
@@ -15,11 +15,11 @@ module Relaton
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def apply_namespace(xpath)
|
18
|
-
xpath
|
19
|
-
gsub(%r{/([a-zA-Z])}, "/xmlns:\\1")
|
20
|
-
gsub(%r{::([a-zA-Z])}, "::xmlns:\\1")
|
21
|
-
gsub(%r{\[([a-zA-Z][a-z0-9A-Z@/]* ?=)}, "[xmlns:\\1")
|
22
|
-
gsub(%r{\[([a-zA-Z][a-z0-9A-Z@/]*\])}, "[xmlns:\\1")
|
18
|
+
xpath
|
19
|
+
.gsub(%r{/([a-zA-Z])}, "/xmlns:\\1")
|
20
|
+
.gsub(%r{::([a-zA-Z])}, "::xmlns:\\1")
|
21
|
+
.gsub(%r{\[([a-zA-Z][a-z0-9A-Z@/]* ?=)}, "[xmlns:\\1")
|
22
|
+
.gsub(%r{\[([a-zA-Z][a-z0-9A-Z@/]*\])}, "[xmlns:\\1")
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
data/relaton-cli.gemspec
CHANGED
@@ -23,18 +23,19 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.required_ruby_version = ">= 2.4.0"
|
24
24
|
|
25
25
|
spec.add_development_dependency "byebug", "~> 11.0"
|
26
|
-
spec.add_development_dependency "debase"
|
26
|
+
# spec.add_development_dependency "debase"
|
27
27
|
spec.add_development_dependency "equivalent-xml", "~> 0.6"
|
28
28
|
spec.add_development_dependency "pry"
|
29
|
-
spec.add_development_dependency "rake"
|
29
|
+
spec.add_development_dependency "rake"
|
30
30
|
spec.add_development_dependency "rspec", "~> 3.0"
|
31
31
|
spec.add_development_dependency "rspec-command", "~> 1.0.3"
|
32
32
|
spec.add_development_dependency "rspec-core", "~> 3.4"
|
33
|
-
spec.add_development_dependency "ruby-debug-ide"
|
33
|
+
# spec.add_development_dependency "ruby-debug-ide"
|
34
34
|
spec.add_development_dependency "simplecov"
|
35
|
+
spec.add_development_dependency "vcr"
|
36
|
+
spec.add_development_dependency "webmock"
|
35
37
|
|
36
|
-
spec.add_runtime_dependency "liquid"
|
37
|
-
spec.add_runtime_dependency "relaton", "
|
38
|
+
spec.add_runtime_dependency "liquid", "~> 4"
|
39
|
+
spec.add_runtime_dependency "relaton", "1.7.pre7"
|
38
40
|
spec.add_runtime_dependency "thor"
|
39
|
-
# spec.add_runtime_dependency 'byebug'
|
40
41
|
end
|
data/templates/_index.liquid
CHANGED
@@ -17,7 +17,12 @@
|
|
17
17
|
<body>
|
18
18
|
<header>
|
19
19
|
<div id="topbar-inner">
|
20
|
-
<div id="title-bar"
|
20
|
+
<div id="title-bar">
|
21
|
+
<div class="doc-access">
|
22
|
+
Generated: {{ date }}{% if metanorma_v != nil %} {{ metanorma_v }}{% endif %}
|
23
|
+
</div>
|
24
|
+
<span>{{ author }}</span>
|
25
|
+
</div>
|
21
26
|
</div>
|
22
27
|
<div class="title-section">
|
23
28
|
<div class="coverpage">
|
@@ -34,6 +39,9 @@
|
|
34
39
|
</main>
|
35
40
|
<footer>
|
36
41
|
<div class="copyright">
|
42
|
+
<div class="doc-access">
|
43
|
+
Generated: {{ date }}{% if metanorma_v != nil %} {{ metanorma_v }}{% endif %}
|
44
|
+
</div>
|
37
45
|
<p class="year">© {{ author }}</p>
|
38
46
|
<p class="message">All rights reserved. Unless otherwise specified, no part of this publication may be reproduced or utilized otherwise in any form or by any means, electronic or mechanical, including photocopying, or posting on the internet or an intranet, without prior written permission.</p>
|
39
47
|
</div>
|