alula-ruby 0.50.1
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 +14 -0
- data/.env.example +8 -0
- data/.github/workflows/gem-push.yml +45 -0
- data/.gitignore +23 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/Dockerfile +6 -0
- data/Gemfile +12 -0
- data/Guardfile +42 -0
- data/README.md +423 -0
- data/Rakefile +6 -0
- data/VERSION.md +84 -0
- data/alula-docker-compose.yml +80 -0
- data/alula.gemspec +38 -0
- data/bin/console +15 -0
- data/bin/docparse +36 -0
- data/bin/genresource +79 -0
- data/bin/setup +8 -0
- data/bin/testauth +24 -0
- data/bin/testprep +9 -0
- data/data/docs/Alula_API_Documentation_2021-04-06.html +16240 -0
- data/docker-compose.yml +11 -0
- data/lib/alula/alula_response.rb +20 -0
- data/lib/alula/api_operations/delete.rb +52 -0
- data/lib/alula/api_operations/list.rb +45 -0
- data/lib/alula/api_operations/request.rb +44 -0
- data/lib/alula/api_operations/save.rb +81 -0
- data/lib/alula/api_resource.rb +196 -0
- data/lib/alula/client.rb +142 -0
- data/lib/alula/errors.rb +169 -0
- data/lib/alula/filter_builder.rb +271 -0
- data/lib/alula/helpers/device_attribute_translations.rb +68 -0
- data/lib/alula/list_object.rb +64 -0
- data/lib/alula/meta.rb +16 -0
- data/lib/alula/monkey_patches.rb +24 -0
- data/lib/alula/oauth.rb +118 -0
- data/lib/alula/pagination.rb +25 -0
- data/lib/alula/procedures/dealer_device_stats_proc.rb +16 -0
- data/lib/alula/procedures/dealer_restore_proc.rb +25 -0
- data/lib/alula/procedures/dealer_suspend_proc.rb +25 -0
- data/lib/alula/procedures/device_assign_proc.rb +23 -0
- data/lib/alula/procedures/device_cellular_history_proc.rb +33 -0
- data/lib/alula/procedures/device_rateplan_get_proc.rb +21 -0
- data/lib/alula/procedures/device_register_proc.rb +31 -0
- data/lib/alula/procedures/device_signal_add_proc.rb +42 -0
- data/lib/alula/procedures/device_signal_delivered_proc.rb +31 -0
- data/lib/alula/procedures/device_signal_update_proc.rb +32 -0
- data/lib/alula/procedures/device_unassign_proc.rb +16 -0
- data/lib/alula/procedures/device_unregister_proc.rb +21 -0
- data/lib/alula/procedures/upload_touchpad_branding_proc.rb +24 -0
- data/lib/alula/procedures/user_plansvideo_price_get.rb +21 -0
- data/lib/alula/procedures/user_transfer_accept.rb +19 -0
- data/lib/alula/procedures/user_transfer_authorize.rb +18 -0
- data/lib/alula/procedures/user_transfer_cancel.rb +18 -0
- data/lib/alula/procedures/user_transfer_deny.rb +19 -0
- data/lib/alula/procedures/user_transfer_reject.rb +19 -0
- data/lib/alula/procedures/user_transfer_request.rb +19 -0
- data/lib/alula/query_interface.rb +142 -0
- data/lib/alula/rate_limit.rb +11 -0
- data/lib/alula/relationship_attributes.rb +107 -0
- data/lib/alula/resource_attributes.rb +206 -0
- data/lib/alula/resources/admin_user.rb +207 -0
- data/lib/alula/resources/billing_program.rb +41 -0
- data/lib/alula/resources/dealer.rb +218 -0
- data/lib/alula/resources/dealer_account_transfer.rb +172 -0
- data/lib/alula/resources/dealer_address.rb +89 -0
- data/lib/alula/resources/dealer_branding.rb +226 -0
- data/lib/alula/resources/dealer_program.rb +75 -0
- data/lib/alula/resources/dealer_suspension_log.rb +49 -0
- data/lib/alula/resources/device.rb +716 -0
- data/lib/alula/resources/device_cellular_status.rb +134 -0
- data/lib/alula/resources/device_charge.rb +70 -0
- data/lib/alula/resources/device_event_log.rb +167 -0
- data/lib/alula/resources/device_program.rb +54 -0
- data/lib/alula/resources/event_trigger.rb +75 -0
- data/lib/alula/resources/event_webhook.rb +47 -0
- data/lib/alula/resources/feature_bysubject.rb +74 -0
- data/lib/alula/resources/feature_plan.rb +57 -0
- data/lib/alula/resources/feature_planvideo.rb +54 -0
- data/lib/alula/resources/feature_price.rb +46 -0
- data/lib/alula/resources/receiver_connection.rb +95 -0
- data/lib/alula/resources/receiver_group.rb +74 -0
- data/lib/alula/resources/revision.rb +91 -0
- data/lib/alula/resources/self.rb +61 -0
- data/lib/alula/resources/station.rb +130 -0
- data/lib/alula/resources/token_exchange.rb +34 -0
- data/lib/alula/resources/user.rb +229 -0
- data/lib/alula/resources/user_address.rb +121 -0
- data/lib/alula/resources/user_phone.rb +116 -0
- data/lib/alula/resources/user_preferences.rb +57 -0
- data/lib/alula/resources/user_pushtoken.rb +75 -0
- data/lib/alula/resources/user_videoprofile.rb +38 -0
- data/lib/alula/rest_resource.rb +17 -0
- data/lib/alula/rpc_resource.rb +40 -0
- data/lib/alula/rpc_response.rb +14 -0
- data/lib/alula/singleton_rest_resource.rb +26 -0
- data/lib/alula/util.rb +107 -0
- data/lib/alula/version.rb +5 -0
- data/lib/alula.rb +135 -0
- data/lib/parser.rb +199 -0
- metadata +282 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Alula
|
|
2
|
+
class RpcResponse
|
|
3
|
+
attr_accessor :request_id, :result, :data
|
|
4
|
+
|
|
5
|
+
def initialize(response)
|
|
6
|
+
@request_id = response.data['id']
|
|
7
|
+
@result = response.data['result']
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def ok?
|
|
11
|
+
@result['success'] == true || (@result['errors'].nil? && @result['error'].nil?)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Alula
|
|
2
|
+
class SingletonRestResource < ApiResource
|
|
3
|
+
def self.resource_url
|
|
4
|
+
if self == SingletonRestResource
|
|
5
|
+
raise NotImplementedError, "SingletonRestResource is an abstract class. You should perform actions on its subclasses (Self, etc.)"
|
|
6
|
+
end
|
|
7
|
+
# Namespaces are separated in object names with periods (.) and in URLs
|
|
8
|
+
# with forward slashes (/), so replace the former with the latter.
|
|
9
|
+
"/rest/v1/#{self.get_resource_path}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def resource_url
|
|
13
|
+
self.class.resource_url
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.retrieve
|
|
17
|
+
instance = new
|
|
18
|
+
instance.refresh
|
|
19
|
+
instance
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(attributes = {})
|
|
23
|
+
super(nil, attributes)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/alula/util.rb
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module Alula
|
|
2
|
+
class Util
|
|
3
|
+
class << self
|
|
4
|
+
#
|
|
5
|
+
# Some fields use odd casing (abbreviations mostly) that require a strict mapping
|
|
6
|
+
# for camelization to work properly
|
|
7
|
+
CAMELIZE_MAPPING = {
|
|
8
|
+
'ce_arming_supervision_trouble_zones' => 'CEArmingSupervisionTroubleZones'
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
# Based on ActiveSupport's underscore method
|
|
12
|
+
# activesupport/lib/active_support/inflector/methods.rb, line 90
|
|
13
|
+
def underscore(camel_cased_word)
|
|
14
|
+
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
|
|
15
|
+
word = camel_cased_word.to_s.gsub(/::/, '/')
|
|
16
|
+
word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
|
|
17
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
|
18
|
+
word.tr!("-", "_")
|
|
19
|
+
word.downcase!
|
|
20
|
+
word
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Check if BVN hex conversion needed
|
|
24
|
+
def convert_hex_crc?(value)
|
|
25
|
+
[32, 33, 34, 35].include? value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def camelize(under_scored_word)
|
|
29
|
+
return CAMELIZE_MAPPING[under_scored_word.to_s] if CAMELIZE_MAPPING.key?(under_scored_word.to_s)
|
|
30
|
+
words = under_scored_word.to_s.split(/-|_/)
|
|
31
|
+
words.each_with_index.map{ |el, i| i == 0 ? el.downcase : el.capitalize }.join
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def upper_camelcase(under_scored_word)
|
|
35
|
+
under_scored_word.to_s.split(/-|_/).map(&:capitalize).join
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def split_on_word(string, separator_match = /-|_/, &block)
|
|
39
|
+
string.to_s.split(separator_match)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# https://stackoverflow.com/questions/9381553/ruby-merge-nested-hash
|
|
43
|
+
def deep_merge(first, second)
|
|
44
|
+
merger = proc do |key, v1, v2|
|
|
45
|
+
if Hash === v1 && Hash === v2
|
|
46
|
+
v1.merge(v2, &merger)
|
|
47
|
+
elsif Array === v1 && Array === v2
|
|
48
|
+
v1 | v2
|
|
49
|
+
elsif [:undefined, nil, :nil].include?(v2)
|
|
50
|
+
v1
|
|
51
|
+
else
|
|
52
|
+
v2
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
first.merge(second.to_h, &merger)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def model_errors_from_response(response)
|
|
60
|
+
errors = response.data['errors']
|
|
61
|
+
return false unless errors
|
|
62
|
+
|
|
63
|
+
error_object = errors.each_with_object({}) do |err, collector|
|
|
64
|
+
#
|
|
65
|
+
# This is duplicitive but it ensures that the model
|
|
66
|
+
# itself will have _some_ kind of error detail if we need to print that out.
|
|
67
|
+
collector[:model] ||= []
|
|
68
|
+
collector[:model] << err['detail']
|
|
69
|
+
|
|
70
|
+
# Error objects contain a bunch of info, but all the good stuff
|
|
71
|
+
# is stored in a sub-object named meta
|
|
72
|
+
#
|
|
73
|
+
# meta looks something like this
|
|
74
|
+
# {
|
|
75
|
+
# fieldName: 'field error description',
|
|
76
|
+
# context: {
|
|
77
|
+
# // extra info about the request
|
|
78
|
+
# }
|
|
79
|
+
# }
|
|
80
|
+
# We need the fieldName to join errors to fields, and context gives extra
|
|
81
|
+
# info that's good for debugging.
|
|
82
|
+
err['meta']&.each_pair do |attribute, val|
|
|
83
|
+
if attribute == 'context'
|
|
84
|
+
collector[:context] ||= []
|
|
85
|
+
collector[:context] << val
|
|
86
|
+
else
|
|
87
|
+
underscore_field = Alula::Util.underscore(attribute).to_sym
|
|
88
|
+
collector[underscore_field] = val
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
#
|
|
94
|
+
# De-duplicate context so we don't have the same info (requestId)
|
|
95
|
+
# existing for every field that failed.
|
|
96
|
+
error_object[:context] = error_object[:context].uniq.compact if error_object[:context]
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# We have UI clients that depend on this being a string.
|
|
100
|
+
# TODO: Update clients and let this be an array?
|
|
101
|
+
error_object[:model] = error_object[:model].uniq.compact.join(', ')
|
|
102
|
+
|
|
103
|
+
error_object
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
data/lib/alula.rb
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require 'alula/version'
|
|
2
|
+
require 'request_store'
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
require_relative 'alula/resource_attributes'
|
|
6
|
+
require_relative 'alula/relationship_attributes'
|
|
7
|
+
require_relative 'alula/api_operations/list'
|
|
8
|
+
require_relative 'alula/api_operations/request'
|
|
9
|
+
require_relative 'alula/api_operations/save'
|
|
10
|
+
require_relative 'alula/api_operations/delete'
|
|
11
|
+
require_relative 'alula/api_resource'
|
|
12
|
+
require_relative 'alula/rpc_resource'
|
|
13
|
+
require_relative 'alula/rpc_response'
|
|
14
|
+
require_relative 'alula/rest_resource'
|
|
15
|
+
require_relative 'alula/meta'
|
|
16
|
+
require_relative 'alula/pagination'
|
|
17
|
+
require_relative 'alula/singleton_rest_resource'
|
|
18
|
+
require_relative 'alula/alula_response'
|
|
19
|
+
require_relative 'alula/filter_builder'
|
|
20
|
+
require_relative 'alula/query_interface'
|
|
21
|
+
require_relative 'alula/util'
|
|
22
|
+
require_relative 'alula/rate_limit'
|
|
23
|
+
|
|
24
|
+
require_relative 'alula/errors'
|
|
25
|
+
require_relative 'alula/client'
|
|
26
|
+
require_relative 'alula/list_object'
|
|
27
|
+
require_relative 'alula/oauth'
|
|
28
|
+
|
|
29
|
+
require_relative 'alula/helpers/device_attribute_translations'
|
|
30
|
+
|
|
31
|
+
require_relative 'alula/resources/billing_program'
|
|
32
|
+
require_relative 'alula/resources/device'
|
|
33
|
+
require_relative 'alula/resources/device_charge'
|
|
34
|
+
require_relative 'alula/resources/device_event_log'
|
|
35
|
+
require_relative 'alula/resources/device_cellular_status'
|
|
36
|
+
require_relative 'alula/resources/device_program'
|
|
37
|
+
require_relative 'alula/resources/dealer_address'
|
|
38
|
+
require_relative 'alula/resources/dealer_account_transfer'
|
|
39
|
+
require_relative 'alula/resources/dealer_suspension_log'
|
|
40
|
+
require_relative 'alula/resources/dealer_program'
|
|
41
|
+
require_relative 'alula/resources/event_trigger'
|
|
42
|
+
require_relative 'alula/resources/event_webhook'
|
|
43
|
+
require_relative 'alula/resources/self'
|
|
44
|
+
require_relative 'alula/resources/user'
|
|
45
|
+
require_relative 'alula/resources/admin_user'
|
|
46
|
+
require_relative 'alula/resources/user_phone'
|
|
47
|
+
require_relative 'alula/resources/user_address'
|
|
48
|
+
require_relative 'alula/resources/user_pushtoken'
|
|
49
|
+
require_relative 'alula/resources/user_preferences'
|
|
50
|
+
require_relative 'alula/resources/user_videoprofile'
|
|
51
|
+
require_relative 'alula/resources/dealer'
|
|
52
|
+
require_relative 'alula/resources/dealer_branding'
|
|
53
|
+
require_relative 'alula/resources/token_exchange'
|
|
54
|
+
require_relative 'alula/resources/receiver_connection'
|
|
55
|
+
require_relative 'alula/resources/receiver_group'
|
|
56
|
+
require_relative 'alula/resources/revision'
|
|
57
|
+
require_relative 'alula/resources/station'
|
|
58
|
+
|
|
59
|
+
require_relative 'alula/resources/feature_plan'
|
|
60
|
+
require_relative 'alula/resources/feature_planvideo'
|
|
61
|
+
require_relative 'alula/resources/feature_price'
|
|
62
|
+
require_relative 'alula/resources/feature_bysubject'
|
|
63
|
+
|
|
64
|
+
require_relative 'alula/procedures/device_cellular_history_proc'
|
|
65
|
+
require_relative 'alula/procedures/device_rateplan_get_proc'
|
|
66
|
+
require_relative 'alula/procedures/device_register_proc'
|
|
67
|
+
require_relative 'alula/procedures/device_unregister_proc'
|
|
68
|
+
require_relative 'alula/procedures/device_assign_proc'
|
|
69
|
+
require_relative 'alula/procedures/device_unassign_proc'
|
|
70
|
+
require_relative 'alula/procedures/device_signal_add_proc'
|
|
71
|
+
require_relative 'alula/procedures/device_signal_update_proc'
|
|
72
|
+
require_relative 'alula/procedures/device_signal_delivered_proc'
|
|
73
|
+
require_relative 'alula/procedures/dealer_device_stats_proc'
|
|
74
|
+
require_relative 'alula/procedures/dealer_suspend_proc'
|
|
75
|
+
require_relative 'alula/procedures/dealer_restore_proc'
|
|
76
|
+
require_relative 'alula/procedures/upload_touchpad_branding_proc'
|
|
77
|
+
require_relative 'alula/procedures/user_transfer_request'
|
|
78
|
+
require_relative 'alula/procedures/user_transfer_cancel'
|
|
79
|
+
require_relative 'alula/procedures/user_transfer_authorize'
|
|
80
|
+
require_relative 'alula/procedures/user_transfer_deny'
|
|
81
|
+
require_relative 'alula/procedures/user_transfer_reject'
|
|
82
|
+
require_relative 'alula/procedures/user_transfer_accept'
|
|
83
|
+
require_relative 'alula/procedures/user_plansvideo_price_get'
|
|
84
|
+
|
|
85
|
+
module Alula
|
|
86
|
+
#
|
|
87
|
+
# A map of API REST resource names to Alula Ruby classes
|
|
88
|
+
# {
|
|
89
|
+
# 'feature-plansvideo' => Alula::FeaturePlanVideo
|
|
90
|
+
# }
|
|
91
|
+
# This is used to determine which class to initialize when working with
|
|
92
|
+
# API query responses, and included models.
|
|
93
|
+
@@resource_map = [
|
|
94
|
+
Alula::BillingProgram,
|
|
95
|
+
Alula::Device,
|
|
96
|
+
Alula::DeviceCharge,
|
|
97
|
+
Alula::DeviceEventLog,
|
|
98
|
+
Alula::DeviceProgram,
|
|
99
|
+
Alula::DealerAddress,
|
|
100
|
+
Alula::DealerProgram,
|
|
101
|
+
Alula::DeviceCellularStatus,
|
|
102
|
+
Alula::Self,
|
|
103
|
+
Alula::User,
|
|
104
|
+
Alula::AdminUser,
|
|
105
|
+
Alula::UserPhone,
|
|
106
|
+
Alula::UserAddress,
|
|
107
|
+
Alula::UserPushtoken,
|
|
108
|
+
Alula::UserPreferences,
|
|
109
|
+
Alula::UserVideoProfile,
|
|
110
|
+
Alula::Dealer,
|
|
111
|
+
Alula::DealerBranding,
|
|
112
|
+
Alula::ReceiverGroup,
|
|
113
|
+
Alula::Station,
|
|
114
|
+
Alula::DealerAccountTransfer,
|
|
115
|
+
Alula::FeaturePlan,
|
|
116
|
+
Alula::FeaturePlanVideo,
|
|
117
|
+
Alula::FeaturePrice,
|
|
118
|
+
Alula::FeatureBySubject,
|
|
119
|
+
].each_with_object({}) do |klass, resource_map|
|
|
120
|
+
resource_map[klass.get_type] = klass
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.class_from_resource_type(type)
|
|
124
|
+
@@resource_map[type] || (raise "Unknown resource name #{type}, you need to map this "\
|
|
125
|
+
'name to a class in the Alula module.')
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.logger
|
|
129
|
+
RequestStore.store['logger'] ||= defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def self.logger=(logger)
|
|
133
|
+
RequestStore.store['logger'] = logger
|
|
134
|
+
end
|
|
135
|
+
end
|
data/lib/parser.rb
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
module Parser
|
|
2
|
+
class Files
|
|
3
|
+
NAMES_TO_RESOURCES = {
|
|
4
|
+
/Alula_API_Documentation_([0-9]{4})-([0-9]{2})-([0-9]{2}).html/ => :reqresp,
|
|
5
|
+
/Alula_Connect_Plus_API_Documentation_([0-9]{4})-([0-9]{2})-([0-9]{2}).html/ => :helix
|
|
6
|
+
}.freeze
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
def load
|
|
10
|
+
NAMES_TO_RESOURCES.each.each_with_object({}) do |(regex, category), coll|
|
|
11
|
+
coll[category] = find_latest_file(regex)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def find_latest_file(pattern)
|
|
16
|
+
Dir.glob('./data/docs/*').grep(pattern).first
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Engine
|
|
22
|
+
DELIMITER = 'hr'
|
|
23
|
+
METHOD_TYPE_REGEX = /Method:|Resource:/
|
|
24
|
+
REST_METHOD_TYPE = /Resource:/
|
|
25
|
+
RPC_METHOD_TYPE = /Method:/
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
#
|
|
29
|
+
# Documentation is a flat structure. HRs split resources, and all elements
|
|
30
|
+
# for each resource are siblings. This walks through the document and pulls out
|
|
31
|
+
# data into a structured hash suitable for further parsing.
|
|
32
|
+
# There are better ways to do this parsing. This is the quick, brute-force way.
|
|
33
|
+
# Note, this implementation uses parse exceptions to indicate the end of parsing.
|
|
34
|
+
# This will be fragile but works for now.
|
|
35
|
+
def parse_doc(category, file)
|
|
36
|
+
doc = Nokogiri::HTML(File.open(file).read)
|
|
37
|
+
|
|
38
|
+
structured = []
|
|
39
|
+
|
|
40
|
+
datum_structure = {
|
|
41
|
+
type: 'This will be rest or rpc',
|
|
42
|
+
name: 'This will be a resource name',
|
|
43
|
+
path: 'This will be the resource path',
|
|
44
|
+
methods: 'This will be a list of supported methods',
|
|
45
|
+
description: 'This will be a description of the resource',
|
|
46
|
+
relationships: 'This will be the relationships',
|
|
47
|
+
parameters: 'This will be the parameters',
|
|
48
|
+
fields: 'This will be the fields'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
current_datum = nil
|
|
52
|
+
|
|
53
|
+
# Parse REST docs.
|
|
54
|
+
doc.css('#mainContent').children.each do |node|
|
|
55
|
+
next if node.name != DELIMITER
|
|
56
|
+
structured << current_datum if current_datum
|
|
57
|
+
current_datum = datum_structure.dup
|
|
58
|
+
current_datum = parse_chunk(current_datum, node) rescue nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Parse RPC docs
|
|
62
|
+
rpc_found = false
|
|
63
|
+
current_datum = nil
|
|
64
|
+
doc.css('#mainContent').children.each do |node|
|
|
65
|
+
# Skip forward until we get to the RPC section
|
|
66
|
+
unless rpc_found
|
|
67
|
+
if node.name == 'h1' and node.text =~ /RPC API/
|
|
68
|
+
rpc_found = true
|
|
69
|
+
else
|
|
70
|
+
next
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
next if node.name != DELIMITER
|
|
75
|
+
|
|
76
|
+
structured << current_datum if current_datum
|
|
77
|
+
current_datum = datum_structure.dup
|
|
78
|
+
current_datum = parse_chunk(current_datum, node) rescue nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
structured
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def parse_chunk(current_datum, node)
|
|
87
|
+
current_datum[:type] = find_type(node)
|
|
88
|
+
current_datum[:name] = find_name(node).strip
|
|
89
|
+
current_datum[:path] = find_path(node).strip
|
|
90
|
+
current_datum[:description] = find_description(node).strip
|
|
91
|
+
current_datum[:parameters] = find_parameters(node)
|
|
92
|
+
|
|
93
|
+
if current_datum[:type] == :rest
|
|
94
|
+
current_datum[:methods] = find_methods(node)
|
|
95
|
+
current_datum[:relationships] = find_relationships(node)
|
|
96
|
+
current_datum[:fields] = find_fields(node)
|
|
97
|
+
else
|
|
98
|
+
current_datum[:methods] = false
|
|
99
|
+
current_datum[:relationships] = false
|
|
100
|
+
current_datum[:fields] = false
|
|
101
|
+
end
|
|
102
|
+
current_datum
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def find_type(node)
|
|
106
|
+
sibling = node.next_sibling
|
|
107
|
+
sibling = sibling.next_sibling until sibling.text =~ METHOD_TYPE_REGEX
|
|
108
|
+
if sibling.text =~ RPC_METHOD_TYPE
|
|
109
|
+
return :rpc
|
|
110
|
+
elsif sibling.text =~ REST_METHOD_TYPE
|
|
111
|
+
return :rest
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def find_name(node)
|
|
116
|
+
sibling = node.next_sibling
|
|
117
|
+
sibling = sibling.next_sibling until sibling.text =~ METHOD_TYPE_REGEX
|
|
118
|
+
sibling.text.gsub(METHOD_TYPE_REGEX, '')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def find_path(node)
|
|
122
|
+
sibling = node.next_sibling
|
|
123
|
+
sibling = sibling.next_sibling until sibling.text =~ /Base URL/
|
|
124
|
+
sibling = sibling.next_sibling until sibling.name == 'p'
|
|
125
|
+
sibling.css('code').text
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def find_methods(node)
|
|
129
|
+
sibling = node.next_sibling
|
|
130
|
+
sibling = sibling.next_sibling until sibling.text =~ /Available methods/
|
|
131
|
+
sibling.css('code').map { |el| el.text }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def find_description(node)
|
|
135
|
+
sibling = node.next_sibling
|
|
136
|
+
sibling = sibling.next_sibling until sibling.text =~ /Description/
|
|
137
|
+
sibling = sibling.next_sibling
|
|
138
|
+
text = []
|
|
139
|
+
until sibling.name == 'h3'
|
|
140
|
+
text << sibling.text
|
|
141
|
+
sibling = sibling.next_sibling
|
|
142
|
+
end
|
|
143
|
+
text.join('')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def find_relationships(node)
|
|
147
|
+
sibling = node.next_sibling
|
|
148
|
+
sibling = sibling.next_sibling until sibling.text =~ /Relationships/
|
|
149
|
+
case sibling.next_sibling.next_sibling.name
|
|
150
|
+
when 'p'
|
|
151
|
+
return false
|
|
152
|
+
when 'table'
|
|
153
|
+
return table_to_hash(sibling.next_sibling.next_sibling)
|
|
154
|
+
else
|
|
155
|
+
raise 'WTF nothing?'
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def find_parameters(node)
|
|
160
|
+
sibling = node.next_sibling
|
|
161
|
+
sibling = sibling.next_sibling until sibling.text =~ /Parameters/
|
|
162
|
+
case sibling.next_sibling.next_sibling.name
|
|
163
|
+
when 'p'
|
|
164
|
+
return false
|
|
165
|
+
when 'table'
|
|
166
|
+
return table_to_hash(sibling.next_sibling.next_sibling)
|
|
167
|
+
else
|
|
168
|
+
raise 'WTF nothing?'
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def find_fields(node)
|
|
173
|
+
sibling = node.next_sibling
|
|
174
|
+
sibling = sibling.next_sibling until sibling.text =~ /Fields/
|
|
175
|
+
case sibling.next_sibling.next_sibling.name
|
|
176
|
+
when 'p'
|
|
177
|
+
return false
|
|
178
|
+
when 'table'
|
|
179
|
+
return table_to_hash(sibling.next_sibling.next_sibling)
|
|
180
|
+
else
|
|
181
|
+
raise 'WTF nothing?'
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
#
|
|
186
|
+
# Transform an HTML table with headers into an array of objects.
|
|
187
|
+
# Object keys are table header text, values are cell value text
|
|
188
|
+
def table_to_hash(node)
|
|
189
|
+
headers = node.css('thead th').map { |el| el.text.downcase }
|
|
190
|
+
node.css('tbody tr').each_with_object([]) do |row, coll|
|
|
191
|
+
vals = row.css('td').map { |c| c.text }
|
|
192
|
+
coll << headers.each_with_index.each_with_object({}) do |(header, index), collector|
|
|
193
|
+
collector[header] = vals[index]
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|