bel 0.5.0 → 0.6.0

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +4 -2
  4. data/bin/bel +60 -3
  5. data/lib/bel/completion_rule.rb +2 -2
  6. data/lib/bel/evidence_model.rb +5 -0
  7. data/lib/bel/evidence_model/bel_parameter.rb +1 -6
  8. data/lib/bel/evidence_model/buffering_evidence_combiner.rb +182 -0
  9. data/lib/bel/evidence_model/evidence.rb +21 -0
  10. data/lib/bel/evidence_model/hash_map_references.rb +33 -0
  11. data/lib/bel/evidence_model/map_references.rb +30 -0
  12. data/lib/bel/evidence_model/map_references_combiner.rb +30 -0
  13. data/lib/bel/evidence_model/streaming_evidence_combiner.rb +37 -0
  14. data/lib/bel/evidence_model/util.rb +84 -0
  15. data/lib/bel/gen.rb +7 -3
  16. data/lib/bel/gen/annotation.rb +5 -2
  17. data/lib/bel/gen/evidence.rb +10 -4
  18. data/lib/bel/gen/namespace.rb +37 -0
  19. data/lib/bel/gen/parameter.rb +71 -0
  20. data/lib/bel/gen/sample_resources.rb +52 -0
  21. data/lib/bel/gen/statement.rb +33 -0
  22. data/lib/bel/gen/term.rb +33 -0
  23. data/lib/bel/language.rb +4 -4
  24. data/lib/bel/libbel/library_resolver.rb +1 -1
  25. data/lib/bel/namespace.rb +3 -3
  26. data/lib/bel/quoting.rb +218 -14
  27. data/lib/bel/translator.rb +11 -3
  28. data/lib/bel/translator/plugins/bel_script/bel_citation_serialization.rb +2 -2
  29. data/lib/bel/translator/plugins/bel_script/bel_discrete_serialization.rb +1 -1
  30. data/lib/bel/translator/plugins/bel_script/bel_yielder.rb +32 -21
  31. data/lib/bel/translator/plugins/bel_script/evidence_serialization.rb +11 -10
  32. data/lib/bel/translator/plugins/bel_script/evidence_yielder.rb +2 -2
  33. data/lib/bel/translator/plugins/bel_script/translator.rb +1 -1
  34. data/lib/bel/translator/plugins/json_evidence/translator.rb +5 -1
  35. data/lib/bel/translator/plugins/rdf/monkey_patch.rb +4 -0
  36. data/lib/bel/translator/plugins/xbel/evidence_handler.rb +23 -12
  37. data/lib/bel/translator/plugins/xbel/evidence_yielder.rb +1 -1
  38. data/lib/bel/translator/plugins/xbel/translator.rb +4 -4
  39. data/lib/bel/translator/plugins/xbel/xbel_yielder.rb +24 -7
  40. data/lib/bel/util.rb +55 -21
  41. data/lib/bel/version.rb +1 -1
  42. metadata +13 -3
  43. data/lib/bel/gen/bel_expression.rb +0 -128
@@ -0,0 +1,33 @@
1
+ require 'bel'
2
+ require_relative '../gen'
3
+ BEL::Gen.soft_require('rantly')
4
+
5
+ module BEL
6
+ module Gen
7
+
8
+ # The {Statement} module defines methods that generate random BEL
9
+ # {BEL::Model::Statement statements}.
10
+ module Statement
11
+ include BEL::Gen::Term
12
+
13
+ # Array of all BEL 1.0 relationships including both short and long form.
14
+ RELATIONSHIPS = BEL::Language::RELATIONSHIPS.each.to_a.flatten.sort.uniq
15
+
16
+ # Returns a randomly chosen relationship.
17
+ # @return [Symbol] the relationship label (short or long form)
18
+ def relationship
19
+ Rantly {
20
+ choose(*RELATIONSHIPS)
21
+ }
22
+ end
23
+
24
+ # Returns a randomly constructed BEL statement.
25
+ # @return [String] the statement label
26
+ def bel_statement
27
+ sub = bel_term
28
+ obj = bel_term
29
+ "#{sub} #{relationship} #{obj}"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ require 'bel'
2
+ require_relative '../gen'
3
+ BEL::Gen.soft_require('rantly')
4
+
5
+ module BEL
6
+ module Gen
7
+
8
+ # The {Term} module defines methods that generate random BEL
9
+ # {BEL::Model::Term terms}.
10
+ module Term
11
+ include BEL::Gen::Parameter
12
+
13
+ # Array of all BEL 1.0 functions including both short and long form.
14
+ FUNCTIONS = BEL::Language::FUNCTIONS.map { |_, fx|
15
+ [ fx[:short_form], fx[:long_form] ]
16
+ }.flatten.sort.uniq
17
+
18
+ # Returns a randomly chosen function.
19
+ # @return [Symbol] the function label (short or long form)
20
+ def function
21
+ Rantly {
22
+ choose(*FUNCTIONS)
23
+ }
24
+ end
25
+
26
+ # Returns a randomly constructed BEL term.
27
+ # @return [String] the term label
28
+ def bel_term
29
+ "#{function}(#{bel_parameter})"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -32,7 +32,7 @@ module BEL
32
32
  else
33
33
  self.value.to_s
34
34
  end
35
- %Q{SET DOCUMENT #{self.name} = #{ensure_quotes(property_value)}}
35
+ %Q{SET DOCUMENT #{self.name} = #{quote_if_needed(property_value)}}
36
36
  end
37
37
  alias_method :to_s, :to_bel
38
38
  end
@@ -94,10 +94,10 @@ module BEL
94
94
 
95
95
  def to_bel
96
96
  if self.value.respond_to? :each
97
- value = self.value.map {|v| always_quote(v)}
97
+ value = self.value.map {|v| quote(v)}
98
98
  value = "{#{value.join(',')}}"
99
99
  else
100
- value = ensure_quotes(self.value)
100
+ value = quote_if_needed(self.value)
101
101
  end
102
102
  "SET #{self.name} = #{value}"
103
103
  end
@@ -125,7 +125,7 @@ module BEL
125
125
  end
126
126
 
127
127
  def to_bel
128
- %Q{SET STATEMENT_GROUP = #{ensure_quotes(self.name)}}
128
+ %Q{SET STATEMENT_GROUP = #{quote_if_needed(self.name)}}
129
129
  end
130
130
 
131
131
  alias_method :to_s, :to_bel
@@ -40,7 +40,7 @@ module BEL
40
40
 
41
41
  def compiled_library(lib_name)
42
42
  exist = File.method(:exist?)
43
- compiled_library_paths(lib_name).select(&exist).first
43
+ compiled_library_paths(lib_name).detect(&exist)
44
44
  end
45
45
 
46
46
  def compiled_library_paths(lib_name)
@@ -239,9 +239,9 @@ module BEL
239
239
  class NamespaceDefinition
240
240
  include Enumerable
241
241
 
242
- attr_reader :prefix
243
- attr_reader :url
244
- attr_reader :rdf_uri
242
+ attr_accessor :prefix
243
+ attr_accessor :url
244
+ attr_accessor :rdf_uri
245
245
 
246
246
  def initialize(prefix, url, rdf_uri = DEFAULT_URI)
247
247
  @prefix = prefix
@@ -1,32 +1,236 @@
1
1
  module BEL
2
+
3
+ # The Quoting module implements quoting rules consistent with BEL
4
+ # and BEL Script. Double quotes are used to group a string together
5
+ # which may contain whitespace or special characters.
6
+ #
7
+ # A value can either be an identifier or a string value. An
8
+ # identifier can only include the characters +[0-9A-Za-z_]+. A string
9
+ # value is necessary when at least one of +[^0-9A-Za-z_]+ exists in
10
+ # the value.
11
+ #
12
+ # Uses:
13
+ #
14
+ # BEL: The BEL parameters must be an identifier or string value.
15
+ #
16
+ # BEL Script: BEL parameters, document property values, and annotation
17
+ # values must be an identifier or string value.
2
18
  module Quoting
3
19
 
4
- NonWordMatcher = Regexp.compile(/[^0-9a-zA-Z_]/)
5
- KeywordMatcher = Regexp.compile(/^(SET|DEFINE|a|g|p|r|m)$/)
20
+ # Declares BEL Script keywords that cause problems with the OpenBEL
21
+ # Framework parser.
22
+ Keywords = %w(SET DEFINE a g p r m)
6
23
 
7
- def ensure_quotes identifier
8
- return "" unless identifier
9
- identifier.to_s.gsub! '"', '\"'
10
- if quotes_required? identifier
11
- %Q{"#{identifier}"}
24
+ # Regular expression that matches one of {Quoting::Keywords}.
25
+ KeywordMatcher = Regexp.compile(/^(#{Keywords.join('|')})$/)
26
+
27
+ # Regular expression that matches on any non-word character.
28
+ NonWordMatcher = Regexp.compile(/[^0-9a-zA-Z_]/)
29
+
30
+ # Regular expression that matches a value surrounded by unescaped
31
+ # double quotes.
32
+ StrictQuotedMatcher = Regexp.compile(/\A".*?(?<!\\)"\Z/m)
33
+
34
+ # Regular expression that matches a value surrounded by double quotes
35
+ # that may be escaped.
36
+ LenientQuotedMatcher = Regexp.compile(/\A".*?"\Z/m)
37
+
38
+ # Regular expression that matches double quotes that are not escaped.
39
+ QuoteNotEscapedMatcher = Regexp.compile(/(?<!\\)"/m)
40
+
41
+ # Returns +value+ surrounded by double quotes. This method is idempotent
42
+ # so +value+ will only be quoted once regardless of how may times the
43
+ # method is called on it.
44
+ #
45
+ # @example Quoting a BEL parameter.
46
+ # quote("apoptotic process")
47
+ # # => "\"apoptotic process\""
48
+ # @example Escaping quotes within a value.
49
+ # quote("vesicle fusion with \"Golgi apparatus\"")
50
+ # # => "\"vesicle fusion with \\\"Golgi apparatus\\\"\""
51
+ #
52
+ # @parameter [#to_s] value a value to be quoted
53
+ # @return [String] value surrounded by double quotes
54
+ def quote(value)
55
+ string = value.to_s
56
+ unquoted = unquote(string)
57
+ escaped = unquoted.gsub(QuoteNotEscapedMatcher, "\\\"")
58
+ %Q{"#{escaped}"}
59
+ end
60
+
61
+ # Returns +value+ with surrounded quotes removed.
62
+ #
63
+ # @example Unquoting a BEL parameter.
64
+ # unquote("\"apoptotic process\"")
65
+ # # => "apoptotic process"
66
+ # @example Escaped quotes are preserved.
67
+ # unquote("\"vesicle fusion with \"Golgi apparatus\"\"")
68
+ #
69
+ # @parameter [#to_s] value a value to be unquoted
70
+ # @return [String] value with surrounding double quotes removed
71
+ def unquote(value)
72
+ string = value.to_s
73
+ if string =~ StrictQuotedMatcher
74
+ string[1...-1]
75
+ else
76
+ string
77
+ end
78
+ end
79
+
80
+ # Returns +value+ with quoting applied only if necessary. A +value+
81
+ # consisting of only word character (e.g. [0-9A-Za-z_]) does not need
82
+ # quoting. A +value+ consisting of at least one non-word character
83
+ # (e.g. [^0-9A-Za-z_]) will requiring quoting.
84
+ #
85
+ # @example Quotes added when value includes spaces.
86
+ # quote_if_needed("apoptotic process")
87
+ # # => "\"apoptotic process\""
88
+ # @example Quotes added when value includes double quote.
89
+ # quote_if_needed("vesicle fusion with \"Golgi apparatus\"")
90
+ # # => "\"vesicle fusion with \\\"Golgi apparatus\\\"\""
91
+ # @example No quotes necessary for identifier.
92
+ # quote_if_needed("AKT1_HUMAN")
93
+ # # => "AKT1_HUMAN"
94
+ #
95
+ # @parameter [#to_s] value that may be quoted
96
+ # @return [String] original value or quoted value
97
+ def quote_if_needed(value)
98
+ if string_value?(value)
99
+ quote(value)
12
100
  else
13
- identifier
101
+ value.to_s
14
102
  end
15
103
  end
16
104
 
105
+ # Returns whether the +value+ is surrounded by double quotes.
106
+ #
107
+ # @example Returns +true+ when value is quoted.
108
+ # quoted?("\"vesicle fusion with \"Golgi apparatus\"")
109
+ # # => true
110
+ # @example Returns +false+ when value is not quoted.
111
+ # quoted?("apoptotic process")
112
+ # # => false
113
+ #
114
+ # @parameter [#to_s] value to test
115
+ # @return [Boolean] +true+ if +value+ is quoted, +false+ if
116
+ # +value+ is not quoted
117
+ def quoted?(value)
118
+ string = value.to_s
119
+ (string =~ LenientQuotedMatcher) != nil
120
+ end
121
+
122
+ # Returns whether the +value+ is not surrounded by double quotes.
123
+ #
124
+ # @example Returns +true+ when value is not quoted.
125
+ # unquoted?("apoptotic process")
126
+ # # => true
127
+ # @example Returns +false+ when value is quoted.
128
+ # unquoted?("\"vesicle fusion with \"Golgi apparatus\"")
129
+ # # => false
130
+ #
131
+ # @parameter [#to_s] value to test
132
+ # @return [Boolean] +true+ if +value+ is not quoted, +false+ if
133
+ # +value+ is quoted
134
+ def unquoted?(value)
135
+ !quoted?(value)
136
+ end
137
+
138
+ # Returns whether the +value+ represents an identifier. An
139
+ # identifier consists of only word characters (e.g. [0-9A-Za-z_]).
140
+ #
141
+ # @example Returns +true+ when representing an identifier.
142
+ # identifier_value?("AKT1_HUMAN")
143
+ # # => true
144
+ # @example Returns +false+ when not representing an identifier.
145
+ # identifier_value?("apoptotic process")
146
+ # # => false
147
+ #
148
+ # @parameter [#to_s] value to test
149
+ # @return [Boolean] +true+ if +value+ is an identifier,
150
+ # +false+ if +value+ is not an identifier
151
+ def identifier_value?(value)
152
+ string = value.to_s
153
+ [NonWordMatcher, KeywordMatcher].none? { |matcher|
154
+ matcher.match string
155
+ }
156
+ end
157
+
158
+ # Returns whether the +value+ represents a string value. A string
159
+ # value consists of at least one non-word character
160
+ # (e.g. [^0-9A-Za-z_]).
161
+ #
162
+ # @example Returns +true+ when representing a string value.
163
+ # string_value?("apoptotic process")
164
+ # # => true
165
+ # @example Returns +false+ when not representing a string value.
166
+ # string_value?("AKT1_HUMAN")
167
+ # # => false
168
+ #
169
+ # @parameter [#to_s] value to test
170
+ # @return [Boolean] +true+ if +value+ is a string value,
171
+ # +false+ if +value+ is not a string value
172
+ def string_value?(value)
173
+ string = value.to_s
174
+ [NonWordMatcher, KeywordMatcher].any? { |matcher|
175
+ matcher.match string
176
+ }
177
+ end
178
+
179
+ ## Deprecated, remove in [0.6.0].
180
+
181
+ # @deprecated Use {#quote_if_needed} instead. Will be removed in a
182
+ # future release.
183
+ def ensure_quotes identifier
184
+ warn <<-DOC.gsub(/^\s+/, '')
185
+ Deprecation Warning
186
+ -------------------
187
+ The BEL::Quoting::ensure_quotes method is deprecated and
188
+ will be removed in a future relase.
189
+ Call module method BEL::Quoting.quote_if_needed instead.
190
+ DOC
191
+ quote_if_needed(identifier)
192
+ end
193
+
194
+ # @deprecated Use {#unquote} instead. Will be removed in a
195
+ # future release.
17
196
  def remove_quotes identifier
18
- identifier.to_s.gsub!(/\A"|"\Z/, '')
19
- identifier
197
+ warn <<-DOC.gsub(/^\s+/, '')
198
+ Deprecation Warning
199
+ -------------------
200
+ The BEL::Quoting::remove_quotes method is deprecated and
201
+ will be removed in a future relase.
202
+ Call module method BEL::Quoting.unquote instead.
203
+ DOC
204
+ unquote(identifier)
20
205
  end
21
206
 
207
+ # @deprecated Use {#quote} instead. Will be removed in a
208
+ # future release.
22
209
  def always_quote identifier
23
- return "" unless identifier
24
- identifier.to_s.gsub! '"', '\"'
25
- %Q("#{identifier}")
210
+ warn <<-DOC.gsub(/^\s+/, '')
211
+ Deprecation Warning
212
+ -------------------
213
+ The BEL::Quoting::always_quote method is deprecated and
214
+ will be removed in a future relase.
215
+ Call module method BEL::Quoting.quote instead.
216
+ DOC
217
+ quote(identifier)
26
218
  end
27
219
 
220
+ # @deprecated Use {#quoted?} or {#unquoted?} instead. Will be removed
221
+ # in a future release.
28
222
  def quotes_required? identifier
29
- [NonWordMatcher, KeywordMatcher].any? { |m| m.match identifier.to_s }
223
+ warn <<-DOC.gsub(/^\s+/, '')
224
+ Deprecation Warning
225
+ -------------------
226
+ The BEL::Quoting::quotes_required? method is deprecated and
227
+ will be removed in a future relase.
228
+ You can use BEL::Quoting.quoted? and BEL::Quoting.unquoted?
229
+ going forward.
230
+ DOC
231
+ [NonWordMatcher, KeywordMatcher].any? { |m|
232
+ m.match identifier.to_s
233
+ }
30
234
  end
31
235
  end
32
236
  end
@@ -39,8 +39,9 @@ module BEL
39
39
  #
40
40
  # @param [#to_s] value an id, media type, or file extension value that
41
41
  # identifies a translator plugin
42
- # @return [#create_translator] if a single a translator plugin was found
43
- # @return [Array<#create_translator>] if multiple translator plugins
42
+ # @return [nil] when no translator plugin was found
43
+ # @return [#create_translator] when single translator plugin was found
44
+ # @return [Array<#create_translator>] when multiple translator plugins
44
45
  # were found
45
46
  def self.for(value)
46
47
  return nil unless value
@@ -57,7 +58,14 @@ module BEL
57
58
  match |= (t.file_extensions.include?(value_symbol))
58
59
  match
59
60
  }
60
- matches.size == 1 ? matches.first : matches
61
+
62
+ if matches.empty?
63
+ nil
64
+ elsif matches.size == 1
65
+ matches.first
66
+ else
67
+ matches
68
+ end
61
69
  end
62
70
  end
63
71
 
@@ -44,12 +44,12 @@ module BEL::Translator::Plugins::BelScript::BelCitationSerialization
44
44
 
45
45
  # Reset cumulative annotations if new citation.
46
46
  if cumulative_citation == nil
47
- bel << %Q{SET STATEMENT_GROUP = #{ensure_quotes(evidence.citation.id)}\n}
47
+ bel << %Q{SET STATEMENT_GROUP = #{quote_if_needed(evidence.citation.id)}\n}
48
48
  cumulative_annotations.clear
49
49
  elsif evidence.citation != cumulative_citation
50
50
  bel << %Q{UNSET STATEMENT_GROUP\n}
51
51
  bel << "\n\n"
52
- bel << %Q{SET STATEMENT_GROUP = #{ensure_quotes(evidence.citation.id)}\n}
52
+ bel << %Q{SET STATEMENT_GROUP = #{quote_if_needed(evidence.citation.id)}\n}
53
53
  cumulative_annotations.clear
54
54
  end
55
55
 
@@ -1,4 +1,4 @@
1
- require_relative 'evidence_serialization.rb'
1
+ require_relative 'evidence_serialization'
2
2
 
3
3
  # BEL Script evidence serialization that writes each evidence with their full
4
4
  # set of annotations (i.e. includes all `SET` and necessary `UNSET` records).
@@ -25,8 +25,11 @@ module BEL::Translator::Plugins
25
25
  # +:citation+ => {BelCitationSerialization}; otherwise the default
26
26
  # of {BelCitationSerialization} is used
27
27
  def initialize(data, options = {})
28
- @data = data
29
- @write_header = options.fetch(:write_header, true)
28
+ @data = data
29
+ @streaming = options.fetch(:streaming, false)
30
+ @write_header = options.fetch(:write_header, true)
31
+ @annotation_reference_map = options.fetch(:annotation_reference_map, nil)
32
+ @namespace_reference_map = options.fetch(:namespace_reference_map, nil)
30
33
 
31
34
  # augment self with BEL serialization stategy.
32
35
  serialization = options[:serialization]
@@ -56,20 +59,31 @@ module BEL::Translator::Plugins
56
59
 
57
60
  def each
58
61
  if block_given?
62
+ combiner =
63
+ if @streaming
64
+ BEL::Model::StreamingEvidenceCombiner.new(@data)
65
+ elsif @annotation_reference_map && @namespace_reference_map
66
+ BEL::Model::MapReferencesCombiner.new(
67
+ @data,
68
+ BEL::Model::HashMapReferences.new(
69
+ @annotation_reference_map,
70
+ @namespace_reference_map
71
+ )
72
+ )
73
+ else
74
+ BEL::Model::BufferingEvidenceCombiner.new(@data)
75
+ end
76
+
59
77
  header_flag = true
60
- @data.each { |evidence|
78
+ combiner.each { |evidence|
61
79
 
62
80
  # serialize evidence
63
81
  bel = to_bel(evidence)
64
82
 
65
83
  if @write_header && header_flag
66
84
  yield document_header(evidence.metadata.document_header)
67
- yield namespaces(
68
- evidence.references.namespaces
69
- )
70
- yield annotations(
71
- evidence.references.annotations
72
- )
85
+ yield namespaces(combiner.namespace_references)
86
+ yield annotations(combiner.annotation_references)
73
87
 
74
88
  yield <<-COMMENT.gsub(/^\s+/, '')
75
89
  ###############################################
@@ -118,18 +132,16 @@ module BEL::Translator::Plugins
118
132
  bel
119
133
  end
120
134
 
121
- def annotations(annotations)
135
+ def annotations(annotation_references)
122
136
  bel = <<-COMMENT.gsub(/^\s+/, '')
123
137
  ###############################################
124
138
  # Annotation Definitions Section
125
139
  COMMENT
126
140
 
127
- return bel unless annotations
141
+ return bel unless annotation_references
128
142
 
129
- annotations.reduce(bel) { |bel, annotation|
130
- keyword = annotation[:keyword]
131
- type = annotation[:type]
132
- domain = annotation[:domain]
143
+ annotation_references.reduce(bel) { |bel, ref|
144
+ keyword, type, domain = ref.values_at(:keyword, :type, :domain)
133
145
  bel << "DEFINE ANNOTATION #{keyword} AS "
134
146
 
135
147
  case type.to_sym
@@ -147,18 +159,17 @@ module BEL::Translator::Plugins
147
159
  bel
148
160
  end
149
161
 
150
- def namespaces(namespaces)
162
+ def namespaces(namespace_references)
151
163
  bel = <<-COMMENT.gsub(/^\s+/, '')
152
164
  ###############################################
153
165
  # Namespace Definitions Section
154
166
  COMMENT
155
167
 
156
- return bel unless namespaces
168
+ return bel unless namespace_references
157
169
 
158
- namespaces.reduce(bel) { |bel, namespace|
159
- keyword = namespace[:keyword]
160
- uri = namespace[:uri]
161
- bel << %Q{DEFINE NAMESPACE #{keyword} AS URL "#{uri}"\n}
170
+ namespace_references.reduce(bel) { |bel, ref|
171
+ keyword, url = ref.values_at(:keyword, :uri)
172
+ bel << %Q{DEFINE NAMESPACE #{keyword} AS URL "#{url}"\n}
162
173
  bel
163
174
  }
164
175
  bel << "\n"