ecfr 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +11 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rspec_parallel +4 -0
- data/.rubocop.yml +28 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +6 -0
- data/Dockerfile +6 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +138 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/ecfr.gemspec +74 -0
- data/lib/ecfr/admin_service/agency/hierarchy.rb +27 -0
- data/lib/ecfr/admin_service/agency.rb +34 -0
- data/lib/ecfr/admin_service/api_documentation.rb +7 -0
- data/lib/ecfr/admin_service/base.rb +26 -0
- data/lib/ecfr/admin_service/build.rb +38 -0
- data/lib/ecfr/admin_service/ecfr_correction/cfr_reference.rb +17 -0
- data/lib/ecfr/admin_service/ecfr_correction.rb +78 -0
- data/lib/ecfr/admin_service/editorial_note/hierarchy.rb +19 -0
- data/lib/ecfr/admin_service/editorial_note.rb +40 -0
- data/lib/ecfr/admin_service/ibr_cfr_range/address.rb +17 -0
- data/lib/ecfr/admin_service/ibr_cfr_range/organization.rb +28 -0
- data/lib/ecfr/admin_service/ibr_cfr_range.rb +67 -0
- data/lib/ecfr/admin_service/issue/change.rb +19 -0
- data/lib/ecfr/admin_service/issue.rb +86 -0
- data/lib/ecfr/admin_service/site_notification.rb +34 -0
- data/lib/ecfr/admin_service/status.rb +7 -0
- data/lib/ecfr/attribute_caster.rb +72 -0
- data/lib/ecfr/attribute_method_definition.rb +92 -0
- data/lib/ecfr/base.rb +71 -0
- data/lib/ecfr/client.rb +318 -0
- data/lib/ecfr/common/hierarchy.rb +35 -0
- data/lib/ecfr/configuration.rb +58 -0
- data/lib/ecfr/constants.rb +21 -0
- data/lib/ecfr/default_documentation_setup.rb +39 -0
- data/lib/ecfr/default_status_setup.rb +46 -0
- data/lib/ecfr/diff_service/base.rb +17 -0
- data/lib/ecfr/diff_service/status.rb +34 -0
- data/lib/ecfr/extensible.rb +45 -0
- data/lib/ecfr/facet_attribute_method_definition.rb +47 -0
- data/lib/ecfr/faraday/user_agent/middleware.rb +14 -0
- data/lib/ecfr/ofr_profile_service/base.rb +20 -0
- data/lib/ecfr/ofr_profile_service/status.rb +7 -0
- data/lib/ecfr/parallel_client.rb +33 -0
- data/lib/ecfr/prince_xml_service/base.rb +17 -0
- data/lib/ecfr/prince_xml_service/pdf.rb +31 -0
- data/lib/ecfr/renderer_service/base.rb +31 -0
- data/lib/ecfr/renderer_service/content.rb +34 -0
- data/lib/ecfr/renderer_service/diff.rb +31 -0
- data/lib/ecfr/renderer_service/origin.rb +56 -0
- data/lib/ecfr/renderer_service/status.rb +7 -0
- data/lib/ecfr/request_representation.rb +12 -0
- data/lib/ecfr/search_service/api_documentation.rb +7 -0
- data/lib/ecfr/search_service/base.rb +23 -0
- data/lib/ecfr/search_service/content_version/count.rb +33 -0
- data/lib/ecfr/search_service/content_version/hierarchical_count.rb +17 -0
- data/lib/ecfr/search_service/content_version/hierarchical_count_node.rb +30 -0
- data/lib/ecfr/search_service/content_version/hierarchichal_result.rb +42 -0
- data/lib/ecfr/search_service/content_version/result.rb +110 -0
- data/lib/ecfr/search_service/content_version/suggestion.rb +76 -0
- data/lib/ecfr/search_service/content_version/summary.rb +27 -0
- data/lib/ecfr/search_service/content_version.rb +85 -0
- data/lib/ecfr/search_service/date_facet.rb +19 -0
- data/lib/ecfr/search_service/facet_base.rb +55 -0
- data/lib/ecfr/search_service/status.rb +7 -0
- data/lib/ecfr/search_service/title_facet.rb +18 -0
- data/lib/ecfr/subscriptions_service/base.rb +19 -0
- data/lib/ecfr/subscriptions_service/status.rb +7 -0
- data/lib/ecfr/subscriptions_service/subscription.rb +97 -0
- data/lib/ecfr/testing/extensions/admin_service/ecfr_correction_extensions.rb +13 -0
- data/lib/ecfr/testing/extensions/admin_service/issue_extensions.rb +13 -0
- data/lib/ecfr/testing/extensions/renderer_service/origin_extensions.rb +13 -0
- data/lib/ecfr/testing/extensions/search_service/content_version_result_extensions.rb +16 -0
- data/lib/ecfr/testing/extensions/search_service/date_facet_extensions.rb +13 -0
- data/lib/ecfr/testing/extensions/versioner_service/ancestors_extensions.rb +20 -0
- data/lib/ecfr/testing/extensions/versioner_service/title_extenstions.rb +16 -0
- data/lib/ecfr/testing/factories/admin_service/cfr_reference_factory.rb +14 -0
- data/lib/ecfr/testing/factories/admin_service/ecfr_correction_factory.rb +31 -0
- data/lib/ecfr/testing/factories/admin_service/issue_change_factory.rb +12 -0
- data/lib/ecfr/testing/factories/admin_service/issue_factory.rb +21 -0
- data/lib/ecfr/testing/factories/common/hierarchy_factory.rb +36 -0
- data/lib/ecfr/testing/factories/renderer_service/origin_factory.rb +32 -0
- data/lib/ecfr/testing/factories/search_service/content_version_count_factory.rb +20 -0
- data/lib/ecfr/testing/factories/search_service/content_version_result_factory.rb +76 -0
- data/lib/ecfr/testing/factories/search_service/date_facet_factory.rb +12 -0
- data/lib/ecfr/testing/factories/versioner_service/ancestors_factory.rb +26 -0
- data/lib/ecfr/testing/factories/versioner_service/metadata_node_info_factory.rb +15 -0
- data/lib/ecfr/testing/factories/versioner_service/node_summary_factory.rb +16 -0
- data/lib/ecfr/testing/factories/versioner_service/structure_factory.rb +57 -0
- data/lib/ecfr/testing/factories/versioner_service/title_factory.rb +36 -0
- data/lib/ecfr/testing/factory_bot_helpers/content_version.rb +38 -0
- data/lib/ecfr/testing/factory_bot_helpers/ecfr_gem_initialize_helpers.rb +51 -0
- data/lib/ecfr/testing/helpers/response_helper.rb +5 -0
- data/lib/ecfr/testing/strategies/ecfr_attribute_hash_strategy.rb +37 -0
- data/lib/ecfr/testing.rb +28 -0
- data/lib/ecfr/version.rb +5 -0
- data/lib/ecfr/versioner_service/ancestors/metadata_node_info.rb +22 -0
- data/lib/ecfr/versioner_service/ancestors/node_summary.rb +54 -0
- data/lib/ecfr/versioner_service/ancestors.rb +152 -0
- data/lib/ecfr/versioner_service/api_documentation.rb +7 -0
- data/lib/ecfr/versioner_service/base.rb +24 -0
- data/lib/ecfr/versioner_service/status.rb +7 -0
- data/lib/ecfr/versioner_service/structure.rb +120 -0
- data/lib/ecfr/versioner_service/title.rb +78 -0
- data/lib/ecfr/versioner_service/xml_content.rb +59 -0
- data/lib/ecfr.rb +90 -0
- data/lib/yard/attribute_handler.rb +87 -0
- data/lib/yard/metadata_handler.rb +87 -0
- metadata +389 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
module Ecfr
|
2
|
+
module VersionerService
|
3
|
+
class Structure < Base
|
4
|
+
STRUCTURE_PATH = "v1/structure"
|
5
|
+
|
6
|
+
#
|
7
|
+
# Retrieves the structure content for given date and hierarchy
|
8
|
+
#
|
9
|
+
# @param [<Date, String, 'current'>] date ISO date string or 'current'
|
10
|
+
# @param [<Integer, String>] title_number CFR title number
|
11
|
+
# @param [<Hash>] options
|
12
|
+
# @option options [String] format (json) the format that should be returned
|
13
|
+
#
|
14
|
+
# @return [<Structure>] a structure representation based on the format requested
|
15
|
+
#
|
16
|
+
def self.find(date, title_number, options = {})
|
17
|
+
default_options = {format: "json"}
|
18
|
+
options = default_options.merge(options.symbolize_keys)
|
19
|
+
|
20
|
+
format = options.delete(:format)
|
21
|
+
|
22
|
+
perform(
|
23
|
+
:get,
|
24
|
+
structure_path(date, title_number, format),
|
25
|
+
params: options.except(:section, :appendix),
|
26
|
+
perform_options: {
|
27
|
+
init_data: {format: format},
|
28
|
+
parse_response: false
|
29
|
+
}
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Provides a url to the structure content for given date and hierarchy
|
35
|
+
#
|
36
|
+
# @param [<Date, String, 'current'>] date ISO date string or 'current'
|
37
|
+
# @param [<Integer, String>] title_number CFR title number
|
38
|
+
# @param [<Hash>] options <description>
|
39
|
+
# @option options [String] format (json) the format that should be returned
|
40
|
+
#
|
41
|
+
# @return [<String>] URL to retreive structure content for given date and hierarchy
|
42
|
+
#
|
43
|
+
def self.url_for(date, title_number, options = {})
|
44
|
+
default_options = {format: "json"}
|
45
|
+
options = default_options.merge(options.symbolize_keys)
|
46
|
+
|
47
|
+
format = options.delete(:format)
|
48
|
+
path = [self::SERVICE_PATH, structure_path(date, title_number, format)].join("/")
|
49
|
+
|
50
|
+
client.build_url(path, options).to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.structure_path(date, title_number, format = "json")
|
54
|
+
"#{STRUCTURE_PATH}/#{date}/title-#{title_number}.#{format}"
|
55
|
+
end
|
56
|
+
private_class_method :structure_path
|
57
|
+
|
58
|
+
# @!attribute data [Hash, Nokogiri::XML::Document]
|
59
|
+
attr_reader :data
|
60
|
+
|
61
|
+
def initialize(data, options = {format: "json"})
|
62
|
+
data = data.body if data.is_a?(Faraday::Response)
|
63
|
+
|
64
|
+
format = options[:format]
|
65
|
+
skip_cache = false
|
66
|
+
|
67
|
+
# we're going to load a response from sideloaded cache
|
68
|
+
if data.is_a?(Hash) && format != "sideloaded"
|
69
|
+
format = "sideloaded"
|
70
|
+
skip_cache = true
|
71
|
+
end
|
72
|
+
|
73
|
+
if format == "json"
|
74
|
+
@data = JSON.parse(data).with_indifferent_access
|
75
|
+
elsif format == "xml"
|
76
|
+
@data = Nokogiri::XML::Document.parse(data)
|
77
|
+
elsif format == "sideloaded"
|
78
|
+
@data = data.with_indifferent_access
|
79
|
+
|
80
|
+
if Ecfr.config.cache_responses && !skip_cache
|
81
|
+
# cache sideloaded response seperately so that a non sideloaded
|
82
|
+
# call to structure can take advantage of cache
|
83
|
+
cache_key = determine_cache_key(options[:referrer].request_data)
|
84
|
+
|
85
|
+
# the client expects to be dealing with response objects, in a
|
86
|
+
# sideload scenario we need to cache a response like object
|
87
|
+
RequestStore[cache_key] = OpenStruct.new(
|
88
|
+
body: data,
|
89
|
+
status: options[:referrer].response_status
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
#
|
98
|
+
# Returns a cache key that would be the same as if this class
|
99
|
+
# wasn't being sideloaded. Currently this only supports
|
100
|
+
# sideloading from the Ancestors endpoint.
|
101
|
+
#
|
102
|
+
# @param [<Hash>] request_data contains information about the
|
103
|
+
# original ancestor request data
|
104
|
+
#
|
105
|
+
# @return [<String>] cache_key
|
106
|
+
#
|
107
|
+
def determine_cache_key(request_data)
|
108
|
+
split = request_data[:path].split("/")
|
109
|
+
date = split[-2]
|
110
|
+
title_number = split[-1].split("-")[1]
|
111
|
+
|
112
|
+
path = "#{self.class::SERVICE_PATH}/#{self.class.send(:structure_path, date, title_number)}"
|
113
|
+
|
114
|
+
params = request_data[:params].except(:structure, :metadata)
|
115
|
+
|
116
|
+
self.class.cache_key(request_data[:method], path, params)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Ecfr
|
2
|
+
module VersionerService
|
3
|
+
#
|
4
|
+
# The titles API endpoint provides information about the status
|
5
|
+
# of each title and the overall system processing status.
|
6
|
+
#
|
7
|
+
# At most times the up_to_date_as_of date will be the same for
|
8
|
+
# all titles. Only when the system import is in process (each
|
9
|
+
# evening) or if reprocessing is being performed will these dates
|
10
|
+
# differ.
|
11
|
+
#
|
12
|
+
# If any title is currently importing or being reprocessed the
|
13
|
+
# processing_in_progress attribute will be set.
|
14
|
+
#
|
15
|
+
# The lastest_amended_on indicates the last time the Title
|
16
|
+
# has a substantive change occur. The latest_issue_date is the
|
17
|
+
# most recent date that we received the title for processing.
|
18
|
+
# These two attributes will generally only differ when we
|
19
|
+
# receive non-sunstantive updates (changes to fix minor spelling,
|
20
|
+
# punctuation, etc.).
|
21
|
+
#
|
22
|
+
class Title < Base
|
23
|
+
result_key :titles
|
24
|
+
|
25
|
+
attribute :name,
|
26
|
+
desc: "name of Title"
|
27
|
+
|
28
|
+
attribute :number,
|
29
|
+
type: :integer,
|
30
|
+
desc: "Title number"
|
31
|
+
|
32
|
+
attribute :processing_in_progress,
|
33
|
+
type: :boolean,
|
34
|
+
desc: "whether the Title is currently being processed"
|
35
|
+
attribute :reserved,
|
36
|
+
type: :boolean,
|
37
|
+
desc: "whether the Title is Reserved"
|
38
|
+
|
39
|
+
attribute :latest_amended_on,
|
40
|
+
type: :date,
|
41
|
+
desc: "the most recent date that the Title has been amended on"
|
42
|
+
attribute :latest_issue_date,
|
43
|
+
type: :date,
|
44
|
+
desc: "the most recent issue of the Title"
|
45
|
+
attribute :up_to_date_as_of,
|
46
|
+
type: :date,
|
47
|
+
desc: "the date through which the Title considered curent"
|
48
|
+
|
49
|
+
metadata_key :meta
|
50
|
+
|
51
|
+
metadata :date,
|
52
|
+
type: :date,
|
53
|
+
desc: "the max up to date value of all Titles"
|
54
|
+
metadata :import_in_progress,
|
55
|
+
type: :boolean,
|
56
|
+
desc: "whether any Title is undergoing processing"
|
57
|
+
|
58
|
+
TITLES_PATH = "v1/titles"
|
59
|
+
|
60
|
+
#
|
61
|
+
# Retreive the list of all Titles
|
62
|
+
#
|
63
|
+
# @param [<Hash>] options
|
64
|
+
# @option options [String] :build_id internal use only - used
|
65
|
+
# to retreive data about a specific build
|
66
|
+
#
|
67
|
+
# @return [<Title>] array of Titles with data
|
68
|
+
#
|
69
|
+
def self.all(options = {})
|
70
|
+
perform(
|
71
|
+
:get,
|
72
|
+
TITLES_PATH,
|
73
|
+
params: options.compact
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Ecfr
|
2
|
+
module VersionerService
|
3
|
+
#
|
4
|
+
# The XML endpoint will return the full XML available
|
5
|
+
# for a given date and hierarchy.
|
6
|
+
#
|
7
|
+
# This is used primarily by our internal import processes
|
8
|
+
# and to provide links to XML from the web UI.
|
9
|
+
#
|
10
|
+
class XmlContent < Base
|
11
|
+
attribute :xml,
|
12
|
+
desc: "XML document"
|
13
|
+
|
14
|
+
XML_PATH = "v1/full"
|
15
|
+
|
16
|
+
#
|
17
|
+
# Retreive the XML for a given date and hierarchy
|
18
|
+
#
|
19
|
+
# @param [<Date, String, 'current'>] date ISO string or 'current'
|
20
|
+
# @param [<Integer, String>] title_number the title of interest
|
21
|
+
# @param [<Hash>] options a hash of hierarchy levels for the content desired - see attributes defined in {Ecfr::Common::Hierarchy} for acceptable keys
|
22
|
+
# @option options [<String>] build_id internal use only - a specific build id
|
23
|
+
#
|
24
|
+
# @return [<XML>] XML for the full title or pared down to the requested hierarchy
|
25
|
+
#
|
26
|
+
def self.find(date, title_number, options = {})
|
27
|
+
new(
|
28
|
+
{
|
29
|
+
xml: get(
|
30
|
+
xml_content_path(date, title_number),
|
31
|
+
options
|
32
|
+
).body
|
33
|
+
}.stringify_keys
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Provides a url to the XML content for a given set of parameters
|
39
|
+
#
|
40
|
+
# @param [<Date, String, 'current'>] date ISO string or 'current'
|
41
|
+
# @param [<Integer, String>] title_number the title of interest
|
42
|
+
# @param [<Hash>] options a hash of hierarchy levels for the content desired - see attributes defined in {Ecfr:Common::Hierarchy} for acceptable keys
|
43
|
+
# @option options [<String>] build_id internal use only - a specific build id
|
44
|
+
#
|
45
|
+
# @return [<String>] URL to retreive XML content for the given parameters
|
46
|
+
#
|
47
|
+
def self.url_for(date, title_number, options = {})
|
48
|
+
path = xml_content_path(date, title_number)
|
49
|
+
|
50
|
+
client.build_url(path, options).to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.xml_content_path(date, title_number)
|
54
|
+
"#{SERVICE_PATH}/#{XML_PATH}/#{date}/title-#{title_number}.xml"
|
55
|
+
end
|
56
|
+
private_class_method :xml_content_path
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/ecfr.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model"
|
4
|
+
require "active_model/type"
|
5
|
+
|
6
|
+
require "active_support"
|
7
|
+
require "active_support/core_ext/class/attribute"
|
8
|
+
require "active_support/core_ext/hash"
|
9
|
+
require "active_support/core_ext/module/delegation"
|
10
|
+
require "active_support/core_ext/string"
|
11
|
+
|
12
|
+
require "faraday"
|
13
|
+
require "faraday/net_http_persistent"
|
14
|
+
require_relative "ecfr/faraday/user_agent/middleware"
|
15
|
+
|
16
|
+
# NOTE: faraday-typhoeus must be installed by consuming
|
17
|
+
# applications
|
18
|
+
begin
|
19
|
+
Gem::Specification.find_by_name("faraday-typhoeus")
|
20
|
+
require "faraday/typhoeus"
|
21
|
+
rescue Gem::LoadError
|
22
|
+
end
|
23
|
+
|
24
|
+
# NOTE: nokogiri must be installed by consuming
|
25
|
+
# applications
|
26
|
+
begin
|
27
|
+
Gem::Specification.find_by_name("nokogiri")
|
28
|
+
require "nokogiri"
|
29
|
+
rescue Gem::LoadError
|
30
|
+
end
|
31
|
+
|
32
|
+
# NOTE: request_store must be installed by consuming
|
33
|
+
# applications
|
34
|
+
begin
|
35
|
+
Gem::Specification.find_by_name("request_store")
|
36
|
+
require "request_store"
|
37
|
+
rescue Gem::LoadError
|
38
|
+
end
|
39
|
+
|
40
|
+
module Ecfr
|
41
|
+
def self.config
|
42
|
+
@config ||= Configuration.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.configure
|
46
|
+
yield(config)
|
47
|
+
config.validate!
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.services
|
51
|
+
[
|
52
|
+
Ecfr::AdminService,
|
53
|
+
Ecfr::DiffService,
|
54
|
+
Ecfr::OfrProfileService,
|
55
|
+
Ecfr::PrinceXmlService,
|
56
|
+
Ecfr::RendererService,
|
57
|
+
Ecfr::SearchService,
|
58
|
+
Ecfr::SubscriptionsService,
|
59
|
+
Ecfr::VersionerService
|
60
|
+
]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# order important
|
65
|
+
require_relative "ecfr/version"
|
66
|
+
require_relative "ecfr/configuration"
|
67
|
+
require_relative "ecfr/constants"
|
68
|
+
require_relative "ecfr/extensible"
|
69
|
+
|
70
|
+
require_relative "ecfr/attribute_caster"
|
71
|
+
require_relative "ecfr/attribute_method_definition"
|
72
|
+
require_relative "ecfr/facet_attribute_method_definition"
|
73
|
+
|
74
|
+
require_relative "ecfr/common/hierarchy"
|
75
|
+
require_relative "ecfr/default_documentation_setup"
|
76
|
+
require_relative "ecfr/default_status_setup"
|
77
|
+
require_relative "ecfr/parallel_client"
|
78
|
+
|
79
|
+
require_relative "ecfr/client"
|
80
|
+
require_relative "ecfr/base"
|
81
|
+
require_relative "ecfr/request_representation"
|
82
|
+
|
83
|
+
require_relative "ecfr/admin_service/base"
|
84
|
+
require_relative "ecfr/diff_service/base"
|
85
|
+
require_relative "ecfr/ofr_profile_service/base"
|
86
|
+
require_relative "ecfr/prince_xml_service/base"
|
87
|
+
require_relative "ecfr/renderer_service/base"
|
88
|
+
require_relative "ecfr/search_service/base"
|
89
|
+
require_relative "ecfr/subscriptions_service/base"
|
90
|
+
require_relative "ecfr/versioner_service/base"
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class AttributeHandler < YARD::Handlers::Ruby::AttributeHandler
|
2
|
+
handles method_call(:attribute)
|
3
|
+
namespace_only
|
4
|
+
|
5
|
+
def process
|
6
|
+
params = statement.parameters(false).dup
|
7
|
+
options = params.pop if params.last.type == :list
|
8
|
+
|
9
|
+
validated_attribute_names(params).each do |name|
|
10
|
+
namespace.attributes[scope][name] ||= SymbolHash[:read => nil, :write => nil]
|
11
|
+
|
12
|
+
object = MethodObject.new(namespace, name, scope)
|
13
|
+
object.file = statement.file
|
14
|
+
object.source = statement.source
|
15
|
+
register(object)
|
16
|
+
|
17
|
+
if options.first.jump(:label).source == "type:"
|
18
|
+
return_type = options.first.jump(:const).source
|
19
|
+
end
|
20
|
+
|
21
|
+
return_type = "String"
|
22
|
+
desc = ""
|
23
|
+
|
24
|
+
options.each do |option|
|
25
|
+
if option.jump(:label).source == "desc:"
|
26
|
+
desc = option.jump(:string_content).source
|
27
|
+
end
|
28
|
+
|
29
|
+
if option.jump(:label).source == "type:"
|
30
|
+
if node_exists?(option, [:var_ref, :const])
|
31
|
+
return_type = option.source.sub(/^type: /, "")
|
32
|
+
elsif node_exists?(option, [:assoc, :symbol_literal])
|
33
|
+
return_type = walk(option, [:assoc, :symbol_literal, :ident]).source.capitalize
|
34
|
+
end
|
35
|
+
|
36
|
+
if node_exists?(option, [:arg_paren])
|
37
|
+
return_type = "[#{return_type.sub(/\AArray\(/,"").sub(/\)\z/, "")}]"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
object.docstring = desc.gsub(/\n/, " ") if desc
|
43
|
+
|
44
|
+
# tags must be added after the docstring
|
45
|
+
object.add_tag(
|
46
|
+
YARD::Tags::Tag.new(:return, nil, return_type)
|
47
|
+
)
|
48
|
+
|
49
|
+
# Register the object explicitly
|
50
|
+
namespace.attributes[scope][name][:read] = object
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Checks whether a node exists at the given jumps
|
56
|
+
#
|
57
|
+
# @param [<AstNode>] ast_node A YARD::Parser::Ruby::AstNode
|
58
|
+
# @param [[<Symbol>]] jumps array of AST node types that should match
|
59
|
+
#
|
60
|
+
# @return [<Type>] <description>
|
61
|
+
#
|
62
|
+
def node_exists?(ast_node, jumps)
|
63
|
+
jumped = walk(ast_node, jumps)
|
64
|
+
!!jumped
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Recusively `jump`s the AstNode node to find a node that
|
69
|
+
# matches all provided jumps. Returns nil if node is
|
70
|
+
# not found.
|
71
|
+
#
|
72
|
+
# In constast `jump` will return any node that matches any
|
73
|
+
# of the provided jumps and return the original node if there
|
74
|
+
# are no matches.
|
75
|
+
#
|
76
|
+
# @param [<AstNode>] ast_node A YARD::Parser::Ruby::AstNode
|
77
|
+
# @param [[<Symbol>]] jumps through array of AST node types to walk
|
78
|
+
#
|
79
|
+
# @return [<AstNode, nil>] the requested node or nil
|
80
|
+
#
|
81
|
+
def walk(ast_node, jumps)
|
82
|
+
jumps = Array(jumps)
|
83
|
+
node = jumps.inject(ast_node) { |o, j| o.jump(j) }
|
84
|
+
|
85
|
+
return node if node != ast_node
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
class MetadataHandler < YARD::Handlers::Ruby::AttributeHandler
|
2
|
+
handles method_call(:metadata)
|
3
|
+
namespace_only
|
4
|
+
|
5
|
+
def process
|
6
|
+
params = statement.parameters(false).dup
|
7
|
+
options = params.pop if params.last.type == :list
|
8
|
+
|
9
|
+
validated_attribute_names(params).each do |name|
|
10
|
+
namespace.attributes[scope][name] ||= SymbolHash[:read => nil, :write => nil]
|
11
|
+
|
12
|
+
object = MethodObject.new(namespace, name, scope)
|
13
|
+
object.file = statement.file
|
14
|
+
object.source = statement.source
|
15
|
+
register(object)
|
16
|
+
|
17
|
+
if options.first.jump(:label).source == "type:"
|
18
|
+
return_type = options.first.jump(:const).source
|
19
|
+
end
|
20
|
+
|
21
|
+
return_type = "String"
|
22
|
+
desc = ""
|
23
|
+
|
24
|
+
options.each do |option|
|
25
|
+
if option.jump(:label).source == "desc:"
|
26
|
+
desc = option.jump(:string_content).source
|
27
|
+
end
|
28
|
+
|
29
|
+
if option.jump(:label).source == "type:"
|
30
|
+
if node_exists?(option, [:var_ref, :const])
|
31
|
+
return_type = option.source.sub(/^type: /, "")
|
32
|
+
elsif node_exists?(option, [:assoc, :symbol_literal])
|
33
|
+
return_type = walk(option, [:assoc, :symbol_literal, :ident]).source.capitalize
|
34
|
+
end
|
35
|
+
|
36
|
+
if node_exists?(option, [:arg_paren])
|
37
|
+
return_type = "[#{return_type.sub(/\AArray\(/,"").sub(/\)\z/, "")}]"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
object.docstring = desc if desc
|
43
|
+
|
44
|
+
# tags must be added after the docstring
|
45
|
+
object.add_tag(
|
46
|
+
YARD::Tags::Tag.new(:return, nil, return_type)
|
47
|
+
)
|
48
|
+
|
49
|
+
# Register the object explicitly
|
50
|
+
namespace.attributes[scope][name][:read] = object
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Checks whether a node exists at the given jumps
|
56
|
+
#
|
57
|
+
# @param [<AstNode>] ast_node A YARD::Parser::Ruby::AstNode
|
58
|
+
# @param [[<Symbol>]] jumps array of AST node types that should match
|
59
|
+
#
|
60
|
+
# @return [<Type>] <description>
|
61
|
+
#
|
62
|
+
def node_exists?(ast_node, jumps)
|
63
|
+
jumped = walk(ast_node, jumps)
|
64
|
+
!!jumped
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Recusively `jump`s the AstNode node to find a node that
|
69
|
+
# matches all provided jumps. Returns nil if node is
|
70
|
+
# not found.
|
71
|
+
#
|
72
|
+
# In constast `jump` will return any node that matches any
|
73
|
+
# of the provided jumps and return the original node if there
|
74
|
+
# are no matches.
|
75
|
+
#
|
76
|
+
# @param [<AstNode>] ast_node A YARD::Parser::Ruby::AstNode
|
77
|
+
# @param [[<Symbol>]] jumps through array of AST node types to walk
|
78
|
+
#
|
79
|
+
# @return [<AstNode, nil>] the requested node or nil
|
80
|
+
#
|
81
|
+
def walk(ast_node, jumps)
|
82
|
+
jumps = Array(jumps)
|
83
|
+
node = jumps.inject(ast_node) { |o, j| o.jump(j) }
|
84
|
+
|
85
|
+
return node if node != ast_node
|
86
|
+
end
|
87
|
+
end
|