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.
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
- def db
15
- @db ||= Relaton::Db.new("#{Dir.home}/.relaton/cache", nil)
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
- def self.start(arguments)
20
- Relaton::Cli::Command.start(arguments)
21
- end
27
+ private
22
28
 
23
- # Relaton
24
- #
25
- # Based on current setup, we need to initiate a Db instance to
26
- # register all of it's supported processor backends. To make it
27
- # easier we have added it as a class method so we can use this
28
- # whenever necessary.
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
- # @param content [Nokogiri::XML::Document]
35
- # @return [RelatonBib::BibliographicItem, RelatonIsoBib::IsoBibliongraphicItem]
36
- def self.parse_xml(doc)
37
- if (proc = Cli.processor(doc))
38
- proc.from_xml(doc.to_s)
39
- else
40
- RelatonBib::XMLParser.from_xml(doc.to_s)
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
- # @param doc [Nokogiri::XML::Element]
45
- # @return [String] Type prefix
46
- def self.processor(doc)
47
- docid = doc.at "docidentifier"
48
- if docid && docid[:type]
49
- proc = Relaton::Registry.instance.by_type(docid[:type])
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
@@ -52,7 +52,7 @@ module Relaton
52
52
  Relaton::Cli::XmlToHtmlRenderer.render(
53
53
  xml_content(file),
54
54
  stylesheet: options[:style],
55
- liquid_dir: options[:template],
55
+ liquid_dir: options[:template]
56
56
  )
57
57
  end
58
58
 
@@ -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, required: true, desc: "Type of standard to get bibliographic entry for"
11
- option :format, aliases: :f, desc: "Output format (xml, bibtex). Default xml."
12
- option :year, aliases: :y, type: :numeric, desc: "Year the standard was published"
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: 'w:UTF-8')
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", "Extract Relaton XML from Metanorma XML file / directory"
21
- option :extension, aliases: :x, default: "rxl", desc: "File extension of Relaton XML files, defaults to 'rxl'"
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 DIRECTORY (containing Relaton-XML or YAML) into a Relaton Collection"
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 collection"
30
- option :new, aliases: :n, type: :boolean, desc: "Use the new Relaton YAML format"
31
- option :extension, aliases: :x, desc: "File extension of destination Relaton file, defaults to 'rxl'"
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 Relaton Collection into multiple files"
38
- option :extension, aliases: :x, default: "rxl", desc: "File extension of Relaton XML files, defaults to 'rxl'"
39
- option :new, aliases: :n, type: :boolean, desc: "Use the new Relaton YAML format"
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 or separate files"
46
- option :extension, aliases: :x, default: "rxl", desc: "File extension of Relaton XML files, defaults to 'rxl'"
47
- option :prefix, aliases: :p, desc: "Filename prefix of individual Relaton XML files, defaults to empty"
48
- option :outdir, aliases: :o, desc: "Output to the specified directory with individual Relaton Bibdata XML files"
49
- option :require, aliases: :r, type: :array, desc: "Require LIBRARY prior to execution"
50
- option :overwrite, aliases: :f, type: :boolean, default: false, desc: "Overwrite the existing file"
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 / Bibcollection YAML (and separate files)"
57
- option :extension, aliases: :x, default: "yaml", desc: "File extension of Relaton YAML files, defaults to 'yaml'"
58
- option :prefix, aliases: :p, desc: "Filename prefix of Relaton XML files, defaults to empty"
59
- option :outdir, aliases: :o, desc: "Output to the specified directory with individual Relaton Bibdata YAML files"
60
- option :require, aliases: :r, type: :array, desc: "Require LIBRARY prior to execution"
61
- option :overwrite, aliases: :f, type: :boolean, default: false, desc: "Overwrite the existing file"
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 HTML"
68
- option :stylesheet, aliases: :s, desc: "Stylesheet file path for rendering HTML index"
69
- option :templatedir, aliases: :t, desc: "Liquid template directory for rendering Relaton items and collection"
70
- option :overwrite, aliases: :f, type: :boolean, default: false, desc: "Overwrite the existing file"
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 YAML into HTML"
77
- option :stylesheet, aliases: :s, desc: "Stylesheet file path for rendering HTML index"
78
- option :templatedir, aliases: :t, desc: "Liquid template directory for rendering Relaton items and collection"
79
- option :overwrite, aliases: :f, type: :boolean, default: false, desc: "Overwrite the existing file"
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 (yaml, bibtex, asciibib)"
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
- item = Relaton::Cli.parse_xml Nokogiri::XML(File.read(file, encoding: "UTF-8"))
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
- private
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
- def supported_type_message
125
- ["Recognised types:", registered_types.sort.join(", ")].join(" ")
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
- def registered_types
129
- @registered_types ||=
130
- Relaton::Registry.instance.processors.each.map { |_n, pr| pr.prefix }
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