tlaw 0.0.2 → 0.1.0.pre
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/.ruby-version +1 -0
- data/CHANGELOG.md +18 -2
- data/README.md +10 -7
- data/examples/demo_base.rb +2 -2
- data/examples/experimental/README.md +3 -0
- data/examples/experimental/afterthedeadline.rb +22 -0
- data/examples/experimental/airvisual.rb +14 -0
- data/examples/experimental/apixu.rb +32 -0
- data/examples/experimental/bing_maps.rb +18 -0
- data/examples/experimental/currencylayer.rb +25 -0
- data/examples/experimental/earthquake.rb +29 -0
- data/examples/experimental/freegeoip.rb +16 -0
- data/examples/experimental/geonames.rb +98 -0
- data/examples/experimental/isfdb.rb +17 -0
- data/examples/experimental/musicbrainz.rb +27 -0
- data/examples/experimental/nominatim.rb +52 -0
- data/examples/experimental/omdb.rb +68 -0
- data/examples/experimental/open_exchange_rates.rb +36 -0
- data/examples/experimental/open_route.rb +27 -0
- data/examples/experimental/open_street_map.rb +16 -0
- data/examples/experimental/quandl.rb +50 -0
- data/examples/experimental/reddit.rb +25 -0
- data/examples/experimental/swapi.rb +27 -0
- data/examples/experimental/tmdb.rb +53 -0
- data/examples/experimental/world_bank.rb +85 -0
- data/examples/experimental/world_bank_climate.rb +77 -0
- data/examples/experimental/wunderground.rb +66 -0
- data/examples/experimental/wunderground_demo.rb +7 -0
- data/examples/forecast_io.rb +16 -16
- data/examples/giphy.rb +4 -4
- data/examples/giphy_demo.rb +1 -1
- data/examples/open_weather_map.rb +64 -60
- data/examples/open_weather_map_demo.rb +4 -4
- data/examples/tmdb_demo.rb +1 -1
- data/examples/urbandictionary_demo.rb +2 -2
- data/lib/tlaw.rb +14 -15
- data/lib/tlaw/api.rb +108 -26
- data/lib/tlaw/api_path.rb +86 -87
- data/lib/tlaw/data_table.rb +15 -10
- data/lib/tlaw/dsl.rb +126 -224
- data/lib/tlaw/dsl/api_builder.rb +47 -0
- data/lib/tlaw/dsl/base_builder.rb +108 -0
- data/lib/tlaw/dsl/endpoint_builder.rb +26 -0
- data/lib/tlaw/dsl/namespace_builder.rb +86 -0
- data/lib/tlaw/endpoint.rb +63 -85
- data/lib/tlaw/formatting.rb +55 -0
- data/lib/tlaw/formatting/describe.rb +86 -0
- data/lib/tlaw/formatting/inspect.rb +52 -0
- data/lib/tlaw/namespace.rb +141 -98
- data/lib/tlaw/param.rb +45 -141
- data/lib/tlaw/param/type.rb +36 -49
- data/lib/tlaw/response_processors.rb +81 -0
- data/lib/tlaw/util.rb +16 -33
- data/lib/tlaw/version.rb +6 -3
- data/tlaw.gemspec +9 -9
- metadata +63 -13
- data/lib/tlaw/param_set.rb +0 -111
- data/lib/tlaw/response_processor.rb +0 -126
data/lib/tlaw/param.rb
CHANGED
@@ -1,155 +1,59 @@
|
|
1
|
-
|
2
|
-
# Base parameter class for working with parameters validation and
|
3
|
-
# converting. You'll never instantiate it directly, just see {DSL#param}
|
4
|
-
# for parameters definition.
|
5
|
-
#
|
6
|
-
class Param
|
7
|
-
# This error is thrown when some value could not be converted to what
|
8
|
-
# this parameter inspects. For example:
|
9
|
-
#
|
10
|
-
# ```ruby
|
11
|
-
# # definition:
|
12
|
-
# param :timestamp, :to_time, format: :to_i
|
13
|
-
# # this means: parameter, when passed, will first be converted with
|
14
|
-
# # method #to_time, and then resulting time will be made into
|
15
|
-
# # unix timestamp with #to_i before passing to API
|
16
|
-
#
|
17
|
-
# # usage:
|
18
|
-
# my_endpoint(timestamp: Time.now) # ok
|
19
|
-
# my_endpoint(timestamp: Date.today) # ok
|
20
|
-
# my_endpoint(timestamp: '2016-06-01') # Nonconvertible! ...unless you've included ActiveSupport :)
|
21
|
-
# ```
|
22
|
-
#
|
23
|
-
Nonconvertible = Class.new(ArgumentError)
|
24
|
-
|
25
|
-
def self.make(name, **options)
|
26
|
-
# NB: Sic. :keyword is nil (not provided) should still
|
27
|
-
# make a keyword argument.
|
28
|
-
if options[:keyword] != false
|
29
|
-
KeywordParam.new(name, **options)
|
30
|
-
else
|
31
|
-
ArgumentParam.new(name, **options)
|
32
|
-
end
|
33
|
-
end
|
1
|
+
# frozen_string_literal: true
|
34
2
|
|
35
|
-
|
3
|
+
require_relative 'param/type'
|
36
4
|
|
37
|
-
|
5
|
+
module TLAW
|
6
|
+
# @private
|
7
|
+
class Param
|
8
|
+
attr_reader :name, :field, :type, :description, :default, :format
|
9
|
+
|
10
|
+
def initialize(
|
11
|
+
name:,
|
12
|
+
field: name,
|
13
|
+
type: nil,
|
14
|
+
description: nil,
|
15
|
+
required: false,
|
16
|
+
keyword: true,
|
17
|
+
default: nil,
|
18
|
+
format: :itself
|
19
|
+
)
|
38
20
|
@name = name
|
39
|
-
@
|
40
|
-
@type = Type.
|
41
|
-
@
|
42
|
-
@
|
43
|
-
@
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
def field
|
59
|
-
options[:field] || name
|
60
|
-
end
|
61
|
-
|
62
|
-
def convert(value)
|
63
|
-
type.convert(value)
|
64
|
-
end
|
65
|
-
|
66
|
-
def format(value)
|
67
|
-
to_url_part(formatter.call(value))
|
68
|
-
end
|
69
|
-
|
70
|
-
def convert_and_format(value)
|
71
|
-
format(convert(value))
|
72
|
-
end
|
73
|
-
|
74
|
-
alias_method :to_h, :options
|
75
|
-
|
76
|
-
def description
|
77
|
-
options[:desc]
|
78
|
-
end
|
79
|
-
|
80
|
-
def describe
|
81
|
-
[
|
82
|
-
'@param', name,
|
83
|
-
("[#{doc_type}]" if doc_type),
|
84
|
-
description,
|
85
|
-
if @options[:enum]
|
86
|
-
"\n Possible values: #{type.values.map(&:inspect).join(', ')}"
|
87
|
-
end,
|
88
|
-
("(default = #{default.inspect})" if default)
|
89
|
-
].compact.join(' ')
|
90
|
-
.derp(&Util::Description.method(:new))
|
91
|
-
end
|
92
|
-
|
93
|
-
private
|
94
|
-
|
95
|
-
attr_reader :formatter
|
96
|
-
|
97
|
-
def doc_type
|
98
|
-
type.to_doc_type
|
99
|
-
end
|
100
|
-
|
101
|
-
def to_url_part(value)
|
102
|
-
case value
|
103
|
-
when Array
|
104
|
-
value.join(',')
|
105
|
-
else
|
106
|
-
value.to_s
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def make_formatter
|
111
|
-
options[:format].derp { |f|
|
112
|
-
return ->(v) { v } unless f
|
113
|
-
return f.to_proc if f.respond_to?(:to_proc)
|
114
|
-
fail ArgumentError, "#{self}: unsupporter formatter #{f}"
|
21
|
+
@field = field
|
22
|
+
@type = Type.coerce(type)
|
23
|
+
@description = description
|
24
|
+
@required = required
|
25
|
+
@keyword = keyword
|
26
|
+
@default = default
|
27
|
+
@format = format
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_h
|
31
|
+
{
|
32
|
+
name: name,
|
33
|
+
field: field,
|
34
|
+
type: type,
|
35
|
+
description: description,
|
36
|
+
required: required?,
|
37
|
+
keyword: keyword?,
|
38
|
+
default: default,
|
39
|
+
format: format
|
115
40
|
}
|
116
41
|
end
|
117
42
|
|
118
|
-
def
|
119
|
-
|
120
|
-
default.inspect
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# @private
|
125
|
-
class ArgumentParam < Param
|
126
|
-
def keyword?
|
127
|
-
false
|
128
|
-
end
|
129
|
-
|
130
|
-
def to_code
|
131
|
-
if required?
|
132
|
-
name.to_s
|
133
|
-
else
|
134
|
-
"#{name}=#{default_to_code}"
|
135
|
-
end
|
43
|
+
def required?
|
44
|
+
@required
|
136
45
|
end
|
137
|
-
end
|
138
46
|
|
139
|
-
# @private
|
140
|
-
class KeywordParam < Param
|
141
47
|
def keyword?
|
142
|
-
|
48
|
+
@keyword
|
143
49
|
end
|
144
50
|
|
145
|
-
def
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
51
|
+
def call(value)
|
52
|
+
type.(value)
|
53
|
+
.yield_self(&format)
|
54
|
+
.yield_self { |val| {field => Array(val).join(',')} }
|
55
|
+
rescue TypeError => e
|
56
|
+
raise TypeError, "#{name}: #{e.message}"
|
151
57
|
end
|
152
58
|
end
|
153
59
|
end
|
154
|
-
|
155
|
-
require_relative 'param/type'
|
data/lib/tlaw/param/type.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TLAW
|
2
4
|
class Param
|
3
5
|
# @private
|
4
6
|
class Type
|
5
7
|
attr_reader :type
|
6
8
|
|
7
|
-
def self.
|
8
|
-
|
9
|
+
def self.default_type
|
10
|
+
@default_type ||= Type.new(nil)
|
11
|
+
end
|
9
12
|
|
13
|
+
def self.coerce(type = nil)
|
10
14
|
case type
|
11
15
|
when nil
|
12
|
-
|
16
|
+
default_type
|
17
|
+
when Type
|
18
|
+
type
|
13
19
|
when Class
|
14
20
|
ClassType.new(type)
|
15
21
|
when Symbol
|
@@ -17,7 +23,7 @@ module TLAW
|
|
17
23
|
when Hash
|
18
24
|
EnumType.new(type)
|
19
25
|
else
|
20
|
-
fail
|
26
|
+
fail ArgumentError, "Undefined type #{type}"
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
@@ -25,88 +31,69 @@ module TLAW
|
|
25
31
|
@type = type
|
26
32
|
end
|
27
33
|
|
28
|
-
def
|
29
|
-
|
34
|
+
def ==(other)
|
35
|
+
other.is_a?(self.class) && other.type == type
|
30
36
|
end
|
31
37
|
|
32
|
-
def
|
33
|
-
|
38
|
+
def call(value)
|
39
|
+
validation_error(value)
|
40
|
+
&.yield_self { |msg|
|
41
|
+
fail TypeError, "expected #{msg}, got #{value.inspect}"
|
42
|
+
}
|
43
|
+
_convert(value)
|
34
44
|
end
|
35
45
|
|
36
|
-
|
37
|
-
|
46
|
+
private
|
47
|
+
|
48
|
+
def validation_error(_value)
|
49
|
+
nil
|
38
50
|
end
|
39
51
|
|
40
52
|
def _convert(value)
|
41
53
|
value
|
42
54
|
end
|
43
|
-
|
44
|
-
def nonconvertible!(value, reason)
|
45
|
-
fail Nonconvertible,
|
46
|
-
"#{self} can't convert #{value.inspect}: #{reason}"
|
47
|
-
end
|
48
55
|
end
|
49
56
|
|
50
57
|
# @private
|
51
58
|
class ClassType < Type
|
52
|
-
|
53
|
-
|
54
|
-
|
59
|
+
private
|
60
|
+
|
61
|
+
def validation_error(value)
|
62
|
+
"instance of #{type}" unless value.is_a?(type)
|
55
63
|
end
|
56
64
|
|
57
65
|
def _convert(value)
|
58
66
|
value
|
59
67
|
end
|
60
|
-
|
61
|
-
def to_doc_type
|
62
|
-
type.name
|
63
|
-
end
|
64
68
|
end
|
65
69
|
|
66
70
|
# @private
|
67
71
|
class DuckType < Type
|
72
|
+
private
|
73
|
+
|
68
74
|
def _convert(value)
|
69
75
|
value.send(type)
|
70
76
|
end
|
71
77
|
|
72
|
-
def
|
73
|
-
value.respond_to?(type)
|
74
|
-
nonconvertible!(value, "not responding to #{type}")
|
75
|
-
end
|
76
|
-
|
77
|
-
def to_doc_type
|
78
|
-
"##{type}"
|
78
|
+
def validation_error(value)
|
79
|
+
"object responding to ##{type}" unless value.respond_to?(type)
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
82
83
|
# @private
|
83
84
|
class EnumType < Type
|
84
|
-
def
|
85
|
-
|
86
|
-
case enum
|
87
|
-
when Hash
|
88
|
-
enum
|
89
|
-
when ->(e) { e.respond_to?(:map) }
|
90
|
-
enum.map { |n| [n, n] }.to_h
|
91
|
-
else
|
92
|
-
fail ArgumentError, "Unparseable enum: #{enum.inspect}"
|
93
|
-
end
|
85
|
+
def possible_values
|
86
|
+
type.keys.map(&:inspect).join(', ')
|
94
87
|
end
|
95
88
|
|
96
|
-
|
97
|
-
type.keys
|
98
|
-
end
|
89
|
+
private
|
99
90
|
|
100
|
-
def
|
101
|
-
type.key?(value)
|
102
|
-
nonconvertible!(
|
103
|
-
value,
|
104
|
-
"is not one of #{type.keys.map(&:inspect).join(', ')}"
|
105
|
-
)
|
91
|
+
def validation_error(value)
|
92
|
+
"one of #{possible_values}" unless type.key?(value)
|
106
93
|
end
|
107
94
|
|
108
95
|
def _convert(value)
|
109
|
-
type
|
96
|
+
type.fetch(value)
|
110
97
|
end
|
111
98
|
end
|
112
99
|
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TLAW
|
4
|
+
# @private
|
5
|
+
module ResponseProcessors
|
6
|
+
# @private
|
7
|
+
module Generators
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def mutate(&block)
|
11
|
+
proc { |hash| hash.tap(&block) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def transform_by_key(key_pattern, &block)
|
15
|
+
proc { |hash| ResponseProcessors.transform_by_key(hash, key_pattern, &block) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def transform_nested(key_pattern, nested_key_pattern = nil, &block)
|
19
|
+
transformer = if nested_key_pattern
|
20
|
+
transform_by_key(nested_key_pattern, &block)
|
21
|
+
else
|
22
|
+
mutate(&block)
|
23
|
+
end
|
24
|
+
proc { |hash| ResponseProcessors.transform_nested(hash, key_pattern, &transformer) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def transform_by_key(value, key_pattern)
|
30
|
+
return value unless value.is_a?(Hash)
|
31
|
+
|
32
|
+
value
|
33
|
+
.map { |k, v| key_pattern === k ? [k, yield(v)] : [k, v] } # rubocop:disable Style/CaseEquality
|
34
|
+
.to_h
|
35
|
+
end
|
36
|
+
|
37
|
+
def transform_nested(value, key_pattern, &block)
|
38
|
+
transform_by_key(value, key_pattern) { |v| v.is_a?(Array) ? v.map(&block) : v }
|
39
|
+
end
|
40
|
+
|
41
|
+
def flatten(value)
|
42
|
+
case value
|
43
|
+
when Hash
|
44
|
+
flatten_hash(value)
|
45
|
+
when Array
|
46
|
+
value.map(&method(:flatten))
|
47
|
+
else
|
48
|
+
value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def datablize(value)
|
53
|
+
case value
|
54
|
+
when Hash
|
55
|
+
value.transform_values(&method(:datablize))
|
56
|
+
when Array
|
57
|
+
if !value.empty? && value.all?(Hash)
|
58
|
+
DataTable.new(value)
|
59
|
+
else
|
60
|
+
value
|
61
|
+
end
|
62
|
+
else
|
63
|
+
value
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def flatten_hash(hash)
|
70
|
+
hash.flat_map do |k, v|
|
71
|
+
v = flatten(v)
|
72
|
+
if v.is_a?(Hash)
|
73
|
+
v.map { |k1, v1| ["#{k}.#{k1}", v1] }
|
74
|
+
else
|
75
|
+
[[k, v]]
|
76
|
+
end
|
77
|
+
end.reject { |_, v| v.nil? }.to_h
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/tlaw/util.rb
CHANGED
@@ -1,45 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TLAW
|
4
|
+
# @private
|
2
5
|
module Util
|
3
6
|
module_function
|
4
7
|
|
5
8
|
def camelize(string)
|
6
|
-
string
|
7
|
-
.sub(/^[a-z\d]*/, &:capitalize)
|
8
|
-
.gsub(%r{(?:_|(/))([a-z\d]*)}i) {
|
9
|
-
"#{$1}#{$2.capitalize}" # rubocop:disable Style/PerlBackrefs
|
10
|
-
}
|
9
|
+
string.sub(/^[a-z\d]*/, &:capitalize)
|
11
10
|
end
|
12
11
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# # "Description of endpoint:\nIt has params:..."
|
19
|
-
#
|
20
|
-
# TLAW::Util::Description.new(str)
|
21
|
-
# # Description of endpoint:
|
22
|
-
# # It has params:...
|
23
|
-
# ```
|
24
|
-
#
|
25
|
-
# TLAW uses it when responds to {APIPath.describe}.
|
26
|
-
#
|
27
|
-
class Description < String
|
28
|
-
alias_method :inspect, :to_s
|
29
|
-
|
30
|
-
def initialize(str)
|
31
|
-
super(str.to_s.gsub(/ +\n/, "\n"))
|
32
|
-
end
|
33
|
-
|
34
|
-
# @private
|
35
|
-
def indent(indentation = ' ')
|
36
|
-
gsub(/(\A|\n)/, '\1' + indentation)
|
37
|
-
end
|
12
|
+
def deindent(string)
|
13
|
+
string
|
14
|
+
.gsub(/^[ \t]+/, '') # first, remove spaces at a beginning of each line
|
15
|
+
.gsub(/\A\n|\n\s*\Z/, '') # then, remove empty lines before and after docs block
|
16
|
+
end
|
38
17
|
|
39
|
-
|
40
|
-
|
41
|
-
|
18
|
+
# Returns [parent, parent.parent, ...]
|
19
|
+
def parents(obj)
|
20
|
+
result = []
|
21
|
+
cursor = obj
|
22
|
+
while (cursor = cursor.parent)
|
23
|
+
result << cursor
|
42
24
|
end
|
25
|
+
result
|
43
26
|
end
|
44
27
|
end
|
45
28
|
end
|