tlaw 0.0.2 → 0.1.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|