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