openbel-api 0.5.1-java → 0.6.1-java
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/.gemspec +14 -6
- data/CHANGELOG.md +32 -0
- data/README.md +2 -2
- data/VERSION +1 -1
- data/app/openbel/api/config.rb +12 -0
- data/app/openbel/api/helpers/base.rb +17 -0
- data/app/openbel/api/helpers/evidence.rb +74 -0
- data/app/openbel/api/helpers/filters.rb +94 -0
- data/app/openbel/api/helpers/translators.rb +73 -0
- data/app/openbel/api/resources/evidence_transform.rb +1 -13
- data/app/openbel/api/routes/authenticate.rb +0 -92
- data/app/openbel/api/routes/base.rb +19 -5
- data/app/openbel/api/routes/datasets.rb +65 -151
- data/app/openbel/api/routes/evidence.rb +10 -95
- data/config/config.yml +4 -6
- data/lib/openbel/api/evidence/api.rb +8 -0
- data/lib/openbel/api/evidence/facet_filter.rb +3 -3
- data/lib/openbel/api/evidence/mongo.rb +304 -44
- data/lib/openbel/api/evidence/mongo_facet.rb +173 -66
- metadata +112 -26
- data/config/async_evidence.rb +0 -12
- data/config/async_jena.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1fa47f8991f91aad490818b0c6e6a5fc8f822ecc
|
4
|
+
data.tar.gz: bbd5f02dfa6c64f060181b029d138960a9ef691f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2fe0173aad3c83200e69361bcd632ee38d19d6b52465f63678fd3385f792cfc491841bee631f4229289e43e843ac3e8e05dddec99d8a64f35508775574a390d
|
7
|
+
data.tar.gz: 28c72b97ae6f3eb3381f51a4194a16b84760549a07c3bcfb31ca7c9a05bfb824f55418d5d3f9fd4017e0d63df2d119a05a27856b6cd4b192909bdfe1a5a9bf21
|
data/.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
'Nick Bargnesi',
|
12
12
|
'William Hayes'
|
13
13
|
]
|
14
|
-
spec.date = %q{
|
14
|
+
spec.date = %q{2016-03-16}
|
15
15
|
spec.email = %q{abargnesi@selventa.com}
|
16
16
|
spec.files = [
|
17
17
|
Dir.glob('app/**/*.{json,rb,ru}'),
|
@@ -34,16 +34,25 @@ Gem::Specification.new do |spec|
|
|
34
34
|
# Dependencies
|
35
35
|
|
36
36
|
## bel.rb
|
37
|
-
spec.add_runtime_dependency 'bel', '0.
|
37
|
+
spec.add_runtime_dependency 'bel', '0.6.0'
|
38
|
+
|
39
|
+
## bel.rb translator dependencies
|
40
|
+
spec.add_runtime_dependency 'json-ld', '1.99.0'
|
41
|
+
spec.add_runtime_dependency 'rdf-json', '1.99.0'
|
42
|
+
spec.add_runtime_dependency 'rdf-rdfa', '1.99.0'
|
43
|
+
spec.add_runtime_dependency 'rdf-rdfxml', '1.99.0'
|
44
|
+
spec.add_runtime_dependency 'rdf-trig', '1.99.0.1'
|
45
|
+
spec.add_runtime_dependency 'rdf-trix', '1.99.0'
|
46
|
+
spec.add_runtime_dependency 'rdf-turtle', '1.99.0'
|
38
47
|
|
39
48
|
## bel.rb plugin - annotation/namespace search
|
40
49
|
spec.add_runtime_dependency 'bel-search-sqlite', '0.4.2'
|
41
50
|
|
42
51
|
## bel.rb plugin - RDF repository using Apache Jena
|
43
|
-
spec.add_runtime_dependency 'bel-rdf-jena', '0.4.
|
52
|
+
spec.add_runtime_dependency 'bel-rdf-jena', '0.4.2'
|
44
53
|
|
45
54
|
## RDF - RDF abstraction
|
46
|
-
spec.add_runtime_dependency 'rdf', '1.99.
|
55
|
+
spec.add_runtime_dependency 'rdf', '1.99.1'
|
47
56
|
|
48
57
|
## Mongo - Faceted search of evidence.
|
49
58
|
spec.add_runtime_dependency 'mongo', '1.12.5'
|
@@ -55,14 +64,13 @@ Gem::Specification.new do |spec|
|
|
55
64
|
spec.add_runtime_dependency 'json_schema', '0.10.0'
|
56
65
|
spec.add_runtime_dependency 'multi_json', '1.11.2'
|
57
66
|
spec.add_runtime_dependency 'oat', '0.4.6'
|
58
|
-
spec.add_runtime_dependency 'puma', '
|
67
|
+
spec.add_runtime_dependency 'puma', '3.1.0'
|
59
68
|
spec.add_runtime_dependency 'rack', '1.6.4'
|
60
69
|
spec.add_runtime_dependency 'rack-cors', '0.4.0'
|
61
70
|
spec.add_runtime_dependency 'rack-handlers', '0.7.0'
|
62
71
|
spec.add_runtime_dependency 'sinatra', '1.4.6'
|
63
72
|
spec.add_runtime_dependency 'sinatra-contrib', '1.4.6'
|
64
73
|
spec.add_runtime_dependency 'jwt', '1.5.2'
|
65
|
-
spec.add_runtime_dependency 'rest-client', '1.8.0'
|
66
74
|
end
|
67
75
|
# vim: ts=2 sw=2:
|
68
76
|
# encoding: utf-8
|
data/CHANGELOG.md
CHANGED
@@ -3,10 +3,36 @@ All notable changes to openbel-api will be documented in this file. The curated
|
|
3
3
|
|
4
4
|
This project adheres to [Semantic Versioning][Semantic Versioning].
|
5
5
|
|
6
|
+
## [0.6.1][0.6.1] - 2016-03-16
|
7
|
+
### Changed
|
8
|
+
- Bumped gems specification date. Requires new version because 0.6.0 was yanked from RubyGems.
|
9
|
+
|
10
|
+
## [0.6.0][0.6.0] - 2016-03-16
|
11
|
+
### Added
|
12
|
+
- Retrieve evidence in a format supported by BEL translator plugins ([Issue 44][44]).
|
13
|
+
- Retrieve dataset evidence in a format supported by BEL translator plugins ([Issue 99][99]).
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
- Dataset evidence collection is missing annotation/namespace URIs ([Issue 95][95]).
|
17
|
+
- Facets are not created for evidence uploaded through a dataset.
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
- MongoDB version 3.2.0 is now required due to use of `$slice` operator in Aggregation queries.
|
21
|
+
|
22
|
+
### Known Issue
|
23
|
+
Datasets are stored with a URI computed from the scheme and host that is serving the OpenBEL API. For example if OpenBEL API is served from `http://web.site.com` then dataset URIs will be of the form `http://web.site.com/api/datasets/{UUID}`. If you change the scheme and host these URIs will be inconsistent and you will not be able to retrieve your datasets. You will have to re-import your documents.
|
24
|
+
|
25
|
+
- See [Issue #102][102].
|
26
|
+
|
27
|
+
-----
|
28
|
+
|
6
29
|
## [0.5.1][0.5.1] - 2015-12-18
|
7
30
|
### Fixed
|
8
31
|
- Authentication error for MongoDB user when faceting on `GET /api/evidence` ([Issue #93][93]).
|
9
32
|
|
33
|
+
### Changed
|
34
|
+
- MongoDB version 3.2.0 is now required due to use of `$slice` operator in Aggregation queries ([Issue ?][]).
|
35
|
+
|
10
36
|
-----
|
11
37
|
|
12
38
|
## [0.5.0][0.5.0] - 2015-12-17
|
@@ -40,10 +66,16 @@ This project adheres to [Semantic Versioning][Semantic Versioning].
|
|
40
66
|
- Retrieve equivalent namespace values from the individual.
|
41
67
|
- Retrieve orthologous namespace values from the individual.
|
42
68
|
|
69
|
+
[0.6.1]: https://github.com/OpenBEL/openbel-api/compare/0.6.0...0.6.1
|
70
|
+
[0.6.0]: https://github.com/OpenBEL/openbel-api/compare/0.5.1...0.6.0
|
43
71
|
[0.5.1]: https://github.com/OpenBEL/openbel-api/compare/0.5.0...0.5.1
|
44
72
|
[0.5.0]: https://github.com/OpenBEL/openbel-api/compare/0.4.0...0.5.0
|
45
73
|
[Semantic Versioning]: http://semver.org
|
46
74
|
[MongoDB User Authentication]: https://github.com/OpenBEL/openbel-api/wiki/Configuring-the-Evidence-Store#mongodb-user-authentication
|
75
|
+
[44]: https://github.com/OpenBEL/openbel-api/issues/44
|
47
76
|
[91]: https://github.com/OpenBEL/openbel-api/issues/91
|
48
77
|
[92]: https://github.com/OpenBEL/openbel-api/issues/92
|
49
78
|
[93]: https://github.com/OpenBEL/openbel-api/issues/93
|
79
|
+
[95]: https://github.com/OpenBEL/openbel-api/issues/95
|
80
|
+
[99]: https://github.com/OpenBEL/openbel-api/issues/99
|
81
|
+
[102]: https://github.com/OpenBEL/openbel-api/issues/102
|
data/README.md
CHANGED
@@ -54,7 +54,7 @@ The OpenBEL API is built to run with [JRuby][JRuby] and [Java 8][Java 8].
|
|
54
54
|
- [JRuby][JRuby], 9.x series (9.0.x.0 is recommended)
|
55
55
|
- The 9.x series is required due to a Ruby language 2.0 requirement.
|
56
56
|
- See "Installation" below for configuring JRuby and isolating the openbel-api application.
|
57
|
-
- [MongoDB][MongoDB], version 3.
|
57
|
+
- [MongoDB][MongoDB], version 3.2 or greater
|
58
58
|
- Follow [MongoDB download][MongoDB download] page for download and installation instructions.
|
59
59
|
- [SQLite][SQLite], version 3.8.0 or greater
|
60
60
|
- Follow [SQLite download][SQLite download] page for download and installation instructions.
|
@@ -236,7 +236,7 @@ API documentation with *Try it* functionality is available [here][OpenBEL API do
|
|
236
236
|
|
237
237
|
-----
|
238
238
|
|
239
|
-
Built with collaboration and :heart: by the [OpenBEL][OpenBEL] community.
|
239
|
+
Built with collaboration and a lot of :heart: by the [OpenBEL][OpenBEL] community.
|
240
240
|
|
241
241
|
[OpenBEL]: http://www.openbel.org
|
242
242
|
[OpenBEL Platform]: https://github.com/OpenBEL/openbel-platform
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.1
|
data/app/openbel/api/config.rb
CHANGED
@@ -92,6 +92,18 @@ module OpenBEL
|
|
92
92
|
]
|
93
93
|
end
|
94
94
|
|
95
|
+
# Check Mongo server version >= 3.2.
|
96
|
+
# The aggregation framework's $slice operator is used which requires 3.2.
|
97
|
+
if mongo_client.server_version.to_s !~ /^3.2/
|
98
|
+
return [
|
99
|
+
true, <<-ERR
|
100
|
+
MongoDB version 3.2 or greater is required.
|
101
|
+
|
102
|
+
MongoDB version: #{mongo_client.server_version}
|
103
|
+
ERR
|
104
|
+
]
|
105
|
+
end
|
106
|
+
|
95
107
|
# Attempt access of database.
|
96
108
|
db = mongo_client.db(mongo[:database])
|
97
109
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module OpenBEL
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
DEFAULT_CONTENT_TYPE = 'application/hal+json'
|
5
|
+
DEFAULT_CONTENT_TYPE_ID = :hal
|
6
|
+
|
7
|
+
def wants_default?
|
8
|
+
if params[:format]
|
9
|
+
return params[:format] == DEFAULT_CONTENT_TYPE
|
10
|
+
end
|
11
|
+
|
12
|
+
request.accept.any? { |accept_entry|
|
13
|
+
accept_entry.to_s == DEFAULT_CONTENT_TYPE
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'bel/util'
|
2
|
+
require_relative 'base'
|
3
|
+
require_relative 'translators'
|
4
|
+
|
5
|
+
module OpenBEL
|
6
|
+
module Helpers
|
7
|
+
|
8
|
+
def render_evidence_collection(
|
9
|
+
name, page_results, start, size, filters,
|
10
|
+
filtered_total, collection_total, evidence_api
|
11
|
+
)
|
12
|
+
# see if the user requested a BEL translator (Accept header or ?format)
|
13
|
+
translator = Translators.requested_translator(request, params)
|
14
|
+
translator_plugin = Translators.requested_translator_plugin(request, params)
|
15
|
+
|
16
|
+
halt 404 unless page_results[:cursor].has_next?
|
17
|
+
|
18
|
+
# Serialize to HAL if they [Accept]ed it, specified it as ?format, or
|
19
|
+
# no translator was found to match request.
|
20
|
+
if wants_default? || !translator
|
21
|
+
facets = page_results[:facets]
|
22
|
+
pager = Pager.new(start, size, filtered_total)
|
23
|
+
evidence = page_results[:cursor].map { |item|
|
24
|
+
item.delete('facets')
|
25
|
+
item
|
26
|
+
}.to_a
|
27
|
+
|
28
|
+
options = {
|
29
|
+
:facets => facets,
|
30
|
+
:start => start,
|
31
|
+
:size => size,
|
32
|
+
:filters => filters,
|
33
|
+
:metadata => {
|
34
|
+
:collection_paging => {
|
35
|
+
:total => collection_total,
|
36
|
+
:total_filtered => pager.total_size,
|
37
|
+
:total_pages => pager.total_pages,
|
38
|
+
:current_page => pager.current_page,
|
39
|
+
:current_page_size => evidence.size,
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
# pager links
|
45
|
+
options[:previous_page] = pager.previous_page
|
46
|
+
options[:next_page] = pager.next_page
|
47
|
+
|
48
|
+
render_collection(evidence, :evidence, options)
|
49
|
+
else
|
50
|
+
extension = translator_plugin.file_extensions.first
|
51
|
+
|
52
|
+
response.headers['Content-Type'] = translator_plugin.media_types.first
|
53
|
+
status 200
|
54
|
+
attachment "#{name}.#{extension}"
|
55
|
+
stream :keep_open do |response|
|
56
|
+
cursor = page_results[:cursor]
|
57
|
+
dataset_evidence = cursor.lazy.map { |evidence|
|
58
|
+
evidence.delete('facets')
|
59
|
+
evidence.delete('_id')
|
60
|
+
evidence = BEL::Model::Evidence.create(BEL.keys_to_symbols(evidence))
|
61
|
+
evidence.bel_statement = BEL::Model::Evidence.parse_statement(evidence)
|
62
|
+
evidence
|
63
|
+
}
|
64
|
+
|
65
|
+
translator.write(
|
66
|
+
dataset_evidence, response,
|
67
|
+
:annotation_reference_map => evidence_api.find_all_annotation_references,
|
68
|
+
:namespace_reference_map => evidence_api.find_all_namespace_references
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module OpenBEL
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# Parse filter query parameters and partition into an {Array}. The first
|
5
|
+
# index will contain the valid filters and the second index will contain
|
6
|
+
# the invalid filters.
|
7
|
+
#
|
8
|
+
# @param [Array<String>] filter_query_params an array of filter strings
|
9
|
+
# encoded in JSON
|
10
|
+
# @return [Array<Array<Hash>, Array<String>] the first index holds the
|
11
|
+
# valid, filter {Hash hashes}; the second index holds the invalid,
|
12
|
+
# filter {String strings}
|
13
|
+
def parse_filters(filter_query_params)
|
14
|
+
filter_query_params.map { |filter_string|
|
15
|
+
begin
|
16
|
+
MultiJson.load filter_string
|
17
|
+
rescue MultiJson::ParseError => ex
|
18
|
+
"#{ex} (filter: #{filter_string})"
|
19
|
+
end
|
20
|
+
}.partition { |filter|
|
21
|
+
filter.is_a?(Hash)
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Retrieve the filters that do not provide category, name, and value keys.
|
26
|
+
#
|
27
|
+
# The parsed, incomplete filters will contain an +:error+ key that provides
|
28
|
+
# an error message intended for the user.
|
29
|
+
#
|
30
|
+
# @param [Array<Hash>] filters an array of filter {Hash hashes}
|
31
|
+
# @return [Array<Hash>] an array of incomplete filter {Hash hashes} that
|
32
|
+
# contain a human-readable error at the +:error+ key
|
33
|
+
def incomplete_filters(filters)
|
34
|
+
filters.select { |filter|
|
35
|
+
['category', 'name', 'value'].any? { |f| !filter.include? f }
|
36
|
+
}.map { |incomplete_filter|
|
37
|
+
category, name, value = incomplete_filter.values_at('category', 'name', 'value')
|
38
|
+
error = <<-MSG.gsub(/^\s+/, '').strip
|
39
|
+
Incomplete filter, category:"#{category}", name:"#{name}", and value:"#{value}".
|
40
|
+
MSG
|
41
|
+
incomplete_filter.merge(:error => error)
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
# Retrieve the filters that represent invalid full-text search values.
|
46
|
+
#
|
47
|
+
# The parsed, invalid full-text search filters will contain an +:error+ key
|
48
|
+
# that provides an error message intended for the user.
|
49
|
+
#
|
50
|
+
# @param [Array<Hash>] filters an array of filter {Hash hashes}
|
51
|
+
# @return [Array<Hash>] an array of invalid full-text search filter
|
52
|
+
# {Hash hashes} that contain a human-readable error at the
|
53
|
+
# +:error+ key
|
54
|
+
def invalid_fts_filters(filters)
|
55
|
+
filters.select { |filter|
|
56
|
+
category, name, value = filter.values_at('category', 'name', 'value')
|
57
|
+
category == 'fts' && name == 'search' && value.to_s.length <= 1
|
58
|
+
}.map { |invalid_fts_filter|
|
59
|
+
error = <<-MSG.gsub(/^\s+/, '').strip
|
60
|
+
Full-text search filter values must be larger than one.
|
61
|
+
MSG
|
62
|
+
invalid_fts_filter.merge(:error => error)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
# Validate the requested filter query strings. If all filters are valid
|
67
|
+
# then return them as {Hash hashes}, otherwise halt 400 Bad Request and
|
68
|
+
# return JSON error response.
|
69
|
+
def validate_filters!
|
70
|
+
filter_query_params = CGI::parse(env["QUERY_STRING"])['filter']
|
71
|
+
valid_filters, invalid_filters = parse_filters(filter_query_params)
|
72
|
+
|
73
|
+
invalid_filters |= incomplete_filters(valid_filters)
|
74
|
+
invalid_filters |= invalid_fts_filters(valid_filters)
|
75
|
+
|
76
|
+
return valid_filters if invalid_filters.empty?
|
77
|
+
|
78
|
+
halt(400, { 'Content-Type' => 'application/json' }, render_json({
|
79
|
+
:status => 400,
|
80
|
+
:msg => "Bad Request",
|
81
|
+
:detail =>
|
82
|
+
invalid_filters.
|
83
|
+
map { |invalid_filter|
|
84
|
+
if invalid_filter.is_a?(Hash) && invalid_filter[:error]
|
85
|
+
invalid_filter[:error]
|
86
|
+
else
|
87
|
+
invalid_filter
|
88
|
+
end
|
89
|
+
}.
|
90
|
+
map(&:to_s)
|
91
|
+
}))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'bel'
|
2
|
+
|
3
|
+
module OpenBEL
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
# Helpers for translator functionality based on user's requested media
|
7
|
+
# type.
|
8
|
+
module Translators
|
9
|
+
|
10
|
+
# Open {::Sinatra::Helpers::Stream} and add the +puts+, +write+, and
|
11
|
+
# +flush+ methods. This is necessary because the RDF.rb writers will call
|
12
|
+
# these methods on the IO (in this case {::Sinatra::Helpers::Stream}).
|
13
|
+
class ::Sinatra::Helpers::Stream
|
14
|
+
|
15
|
+
# Write each string in +args*, new-line delimited, to the stream.
|
16
|
+
def puts(*args)
|
17
|
+
self << (
|
18
|
+
args.map { |string| "#{string.encode(Encoding::UTF_8)}\n" }.join
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Write the string to the stream.
|
23
|
+
def write(string)
|
24
|
+
self << string.encode(Encoding::UTF_8)
|
25
|
+
end
|
26
|
+
|
27
|
+
# flush is a no-op; flushing is handled by sinatra/rack server
|
28
|
+
def flush; end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Find a bel.rb translator plugin by value. The value is commonly the
|
32
|
+
# id, file extension, or media type associated with the translator
|
33
|
+
# plugin.
|
34
|
+
#
|
35
|
+
# @param [#to_s] value used to look up translator plugin registered
|
36
|
+
# with bel.rb
|
37
|
+
# @return [BEL::Translator] the translator instance; or +nil+ if one
|
38
|
+
# cannot be found
|
39
|
+
def self.for(value)
|
40
|
+
BEL.translator(symbolize_value(value))
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.plugin_for(value)
|
44
|
+
BEL::Translator::Plugins.for(symbolize_value(value))
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.requested_translator_plugin(request, params)
|
48
|
+
if params && params[:format]
|
49
|
+
self.plugin_for(params[:format])
|
50
|
+
else
|
51
|
+
request.accept.flat_map { |accept_entry|
|
52
|
+
self.plugin_for(accept_entry)
|
53
|
+
}.compact.first
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.requested_translator(request, params)
|
58
|
+
if params && params[:format]
|
59
|
+
self.for(params[:format])
|
60
|
+
else
|
61
|
+
request.accept.flat_map { |accept_entry|
|
62
|
+
self.for(accept_entry)
|
63
|
+
}.compact.first
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.symbolize_value(value)
|
68
|
+
value.to_s.to_sym
|
69
|
+
end
|
70
|
+
private_class_method :symbolize_value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -94,23 +94,11 @@ module OpenBEL
|
|
94
94
|
|
95
95
|
def free_annotation(name, value)
|
96
96
|
{
|
97
|
-
:name =>
|
97
|
+
:name => name,
|
98
98
|
:value => value
|
99
99
|
}
|
100
100
|
end
|
101
101
|
|
102
|
-
def normalize_annotation_name(name, options = {})
|
103
|
-
name_s = name.to_s
|
104
|
-
|
105
|
-
if name_s.empty?
|
106
|
-
nil
|
107
|
-
else
|
108
|
-
name_s.
|
109
|
-
split(%r{[^a-zA-Z0-9]+}).
|
110
|
-
map! { |word| word.capitalize }.
|
111
|
-
join
|
112
|
-
end
|
113
|
-
end
|
114
102
|
end
|
115
103
|
|
116
104
|
class AnnotationGroupingTransform
|