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.
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