reality 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/CHANGELOG.md +20 -0
- data/README.md +104 -407
- data/bin/reality +7 -10
- data/config/demo.yml +1 -0
- data/lib/reality.rb +11 -6
- data/lib/reality/command_line.rb +124 -0
- data/lib/reality/config.rb +10 -3
- data/lib/reality/definitions/helpers.rb +1 -0
- data/lib/reality/definitions/wikidata.rb +10 -1
- data/lib/reality/definitions/wikipedia_character.rb +1 -0
- data/lib/reality/definitions/wikipedia_city.rb +1 -0
- data/lib/reality/definitions/wikipedia_continent.rb +1 -0
- data/lib/reality/definitions/wikipedia_country.rb +1 -0
- data/lib/reality/definitions/wikipedia_musical_artist.rb +1 -0
- data/lib/reality/definitions/wikipedia_person.rb +1 -0
- data/lib/reality/entity.rb +239 -51
- data/lib/reality/entity/coercion.rb +20 -0
- data/lib/reality/entity/wikidata_predicates.rb +1 -0
- data/lib/reality/entity/wikipedia_type.rb +1 -0
- data/lib/reality/extras/geonames.rb +5 -3
- data/lib/reality/extras/quandl.rb +57 -0
- data/lib/reality/geo.rb +67 -6
- data/lib/reality/list.rb +76 -6
- data/lib/reality/measure.rb +17 -9
- data/lib/reality/methods.rb +7 -3
- data/lib/reality/names.rb +46 -0
- data/lib/reality/refinements.rb +1 -0
- data/lib/reality/tz_offset.rb +57 -4
- data/lib/reality/util/formatters.rb +1 -0
- data/lib/reality/util/parsers.rb +1 -0
- data/lib/reality/version.rb +1 -1
- data/lib/reality/wikidata.rb +25 -177
- data/lib/reality/wikidata/query.rb +106 -0
- data/reality.gemspec +5 -7
- metadata +20 -56
@@ -71,6 +71,26 @@ module Reality
|
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
|
+
def to_simple_type(val)
|
75
|
+
case val
|
76
|
+
when Rational
|
77
|
+
val.to_f
|
78
|
+
when nil, Numeric, String, Symbol
|
79
|
+
val
|
80
|
+
when Array
|
81
|
+
val.map{|v| to_simple_type(v)}
|
82
|
+
#when Hash
|
83
|
+
#val.map{|k, v| [to_simple_type(k), to_simple_type(v)]}.to_h
|
84
|
+
when Entity
|
85
|
+
val.loaded? ? val.to_h : val.to_s
|
86
|
+
|
87
|
+
when ->(v){v.respond_to?(:to_h)}
|
88
|
+
val.to_h
|
89
|
+
else
|
90
|
+
val.to_s
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
74
94
|
end
|
75
95
|
end
|
76
96
|
end
|
@@ -12,10 +12,12 @@ module Reality
|
|
12
12
|
private
|
13
13
|
|
14
14
|
def guess_timezone
|
15
|
-
Timezone::
|
15
|
+
Timezone::Lookup.config(:geonames) do |c|
|
16
|
+
c.username = Reality.config.fetch('keys', 'geonames')
|
17
|
+
end
|
16
18
|
|
17
|
-
gnzone = Timezone
|
18
|
-
gnzone && TZInfo::Timezone.new(gnzone.
|
19
|
+
gnzone = Timezone.lookup(lat.to_f, lng.to_f)
|
20
|
+
gnzone && TZInfo::Timezone.new(gnzone.name)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'quandl'
|
2
|
+
|
3
|
+
module Reality
|
4
|
+
module Extras
|
5
|
+
module Quandl
|
6
|
+
class Economy
|
7
|
+
def initialize(country)
|
8
|
+
@country = country
|
9
|
+
end
|
10
|
+
|
11
|
+
def gdp
|
12
|
+
gdp = fetch("ODA/#{@country.iso3_code}_NGDPD")
|
13
|
+
gdp = gdp.to_i * 1_000_000_000
|
14
|
+
Reality::Measure(gdp, '$')
|
15
|
+
end
|
16
|
+
|
17
|
+
def inflation
|
18
|
+
inflation = fetch("ODA/#{@country.iso3_code}_PCPIPCH")
|
19
|
+
Reality::Measure(inflation, '%')
|
20
|
+
end
|
21
|
+
|
22
|
+
def unemployment
|
23
|
+
unemployment = fetch("ODA/#{@country.iso3_code}_LUR")
|
24
|
+
Reality::Measure(unemployment, '%')
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
"#<Reality::Quandl::Economy (%s)>" % [@country.name]
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def fetch(code)
|
34
|
+
::Quandl::ApiConfig.api_key ||= Reality.config.fetch('keys', 'quandl')
|
35
|
+
database, dataset = code.split('/')
|
36
|
+
data = ::Quandl::Data.all({ params: { database_code: database, dataset_code: dataset }})
|
37
|
+
data.values.select { |v| v['date'] <= Date.today }.sort_by { |d| d['date'] }.last.value
|
38
|
+
rescue ::Quandl::NotFoundError
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module CountryEconomics
|
44
|
+
def economy
|
45
|
+
@economy ||= Extras::Quandl::Economy.new(self)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.included(reality)
|
50
|
+
reality.config.register('keys', 'quandl',
|
51
|
+
desc: 'Quandl API key. Can be obtained here: http://quandl.com')
|
52
|
+
|
53
|
+
reality::Country.include CountryEconomics
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/reality/geo.rb
CHANGED
@@ -4,11 +4,30 @@ Geokit::default_units = :kms # TODO: use global settings
|
|
4
4
|
|
5
5
|
module Reality
|
6
6
|
module Geo
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
7
|
+
# Keeps information about coordinates point.
|
8
|
+
# All services that are based on particular point in space (Weather, Time) depend on it.
|
9
|
+
# Many Entity types can have coordinates, they can be accessed as #coord method which returns this class.
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# ```ruby
|
14
|
+
# Reality::Entity('Mississippi').coord
|
15
|
+
# # => #<Reality::Geo::Coord(33°0′0″N,90°0′0″W)>
|
16
|
+
# ```
|
17
|
+
#
|
18
|
+
# Usage examples:
|
19
|
+
#
|
20
|
+
# ```ruby
|
21
|
+
# coord = Reality::Geo::Coord.new(50.45, 30.52)
|
22
|
+
# # => #<Reality::Geo::Coord(50°27′0″N,30°31′24″E)>
|
23
|
+
# coord.sunrise
|
24
|
+
# # => 2016-03-31 03:35:22 UTC
|
25
|
+
# coord.distance_to(Reality::Entity('London'))
|
26
|
+
# # => #<Reality::Measure(2,135 km)>
|
27
|
+
# ```
|
28
|
+
#
|
29
|
+
# Uses Geokit for some operations like #distance_to, #close_to? etc.
|
10
30
|
#
|
11
|
-
# GeoKit will be used at its time.
|
12
31
|
class Coord
|
13
32
|
attr_reader :lat, :lng
|
14
33
|
|
@@ -44,33 +63,52 @@ module Reality
|
|
44
63
|
sign * (d.abs.to_i + Rational(m.to_i) / 60 + Rational(s.to_f) / 3600)
|
45
64
|
end
|
46
65
|
end
|
47
|
-
|
66
|
+
|
67
|
+
# @param latitude
|
68
|
+
# @param longitude
|
48
69
|
def initialize(lat, lng)
|
49
70
|
@lat, @lng = Rational(lat), Rational(lng)
|
50
71
|
end
|
51
72
|
|
73
|
+
# @param point - Coordinates, e.g "50.45,30.52", [50.45, 30.52], Coord or Entity instance
|
74
|
+
#
|
75
|
+
# @return [Reality::Measure]
|
52
76
|
def distance_to(point)
|
53
77
|
destination_coords = normalize_point(point).to_s
|
54
78
|
res = Geokit::LatLng.distance_between(to_s, destination_coords, formula: :sphere)
|
55
79
|
Reality::Measure(res, 'km')
|
56
80
|
end
|
57
81
|
|
82
|
+
# @param point - Coordinates, e.g "50.45,30.52", [50.45, 30.52], Coord or Entity instance
|
83
|
+
#
|
84
|
+
# @return [Reality::Measure]
|
58
85
|
def direction_to(point)
|
59
86
|
destination_coords = normalize_point(point).to_s
|
60
87
|
res = Geokit::LatLng.heading_between(to_s, destination_coords)
|
61
88
|
Reality::Measure(res, '°')
|
62
89
|
end
|
63
90
|
|
91
|
+
# @param direction - angle from the North by clock, e.g. '90' means 'to the east'
|
92
|
+
# @param distance - km, e.g. 100
|
93
|
+
#
|
94
|
+
# @return [Reality::Geo::Coord]
|
64
95
|
def endpoint(direction, distance)
|
65
96
|
res = Geokit::LatLng.endpoint(to_s, direction.to_f, distance.to_f)
|
66
97
|
Coord.new res.lat, res.lng
|
67
98
|
end
|
68
99
|
|
100
|
+
# @param point - Coordinates, e.g "50.45,30.52", [50.45, 30.52], Coord or Entity instance
|
101
|
+
# @param radius - km, e.g. 10
|
102
|
+
#
|
103
|
+
# @return [true, false]
|
69
104
|
def close_to?(point, radius)
|
70
105
|
area = Geokit::Bounds.from_point_and_radius(to_s, radius.to_f)
|
71
106
|
area.contains?(normalize_point(point).to_s)
|
72
107
|
end
|
73
108
|
|
109
|
+
# Latitude in "degrees minutes seconds" format
|
110
|
+
#
|
111
|
+
# @return [Array]
|
74
112
|
def lat_dms(direction = true)
|
75
113
|
seconds = (lat.abs % 1.0) * 3600.0
|
76
114
|
d, m, s = lat.to_i, (seconds / 60).to_i, (seconds % 60)
|
@@ -81,6 +119,9 @@ module Reality
|
|
81
119
|
end
|
82
120
|
end
|
83
121
|
|
122
|
+
# Longitude in "degrees minutes seconds" format
|
123
|
+
#
|
124
|
+
# @return [Array]
|
84
125
|
def lng_dms(direction = true)
|
85
126
|
seconds = (lng.abs % 1.0) * 3600.0
|
86
127
|
d, m, s = lng.to_i, (seconds / 60).to_i, (seconds % 60)
|
@@ -94,13 +135,18 @@ module Reality
|
|
94
135
|
def to_s
|
95
136
|
"#{lat.to_f},#{lng.to_f}"
|
96
137
|
end
|
138
|
+
alias_method :latlng, :to_s
|
139
|
+
|
140
|
+
def to_h
|
141
|
+
{lat: lat.to_f, lng: lng.to_f}
|
142
|
+
end
|
97
143
|
|
98
144
|
def ==(other)
|
99
145
|
other.is_a?(self.class) && lat == other.lat && lng == self.lng
|
100
146
|
end
|
101
147
|
|
102
148
|
def inspect
|
103
|
-
"#<%s(%i°%i
|
149
|
+
"#<%s(%i°%i′%.0f″%s,%i°%i′%.0f″%s)>" % [self.class, *lat_dms, *lng_dms]
|
104
150
|
end
|
105
151
|
|
106
152
|
def sunrise(date = Date.today)
|
@@ -111,8 +157,23 @@ module Reality
|
|
111
157
|
SunTimes.new.set(date, lat.to_f, lng.to_f)
|
112
158
|
end
|
113
159
|
|
160
|
+
# Links to popular maps sites with this point marked
|
161
|
+
# Maps: openstreetmap, google maps, wikimapia
|
162
|
+
#
|
163
|
+
# @return [Hash]
|
164
|
+
def links
|
165
|
+
param = {lat: lat.to_f, lng: lng.to_f, latlng: latlng}
|
166
|
+
Hashie::Mash.new(EXTERNAL_LINKS.map{|key, pattern| [key, pattern % param]}.to_h)
|
167
|
+
end
|
168
|
+
|
114
169
|
private
|
115
170
|
|
171
|
+
EXTERNAL_LINKS = {
|
172
|
+
osm: "https://www.openstreetmap.org/?mlat=%{lat}&mlon=%{lng}&zoom=12&layers=M",
|
173
|
+
google: "https://maps.google.com/maps?ll=%{latlng}&q=%{latlng}&hl=en&t=m&z=12",
|
174
|
+
wikimapia: "http://wikimapia.org/#lang=en&lat=%{lat}&lon=%{lng}&z=12&m=w"
|
175
|
+
}
|
176
|
+
|
116
177
|
def normalize_point(point)
|
117
178
|
return point if point.is_a?(Coord)
|
118
179
|
point.coord if point.respond_to?(:coord)
|
data/lib/reality/list.rb
CHANGED
@@ -1,16 +1,59 @@
|
|
1
1
|
module Reality
|
2
|
+
# List is subclass of Array, which allows effective entities storing
|
3
|
+
# and loading.
|
4
|
+
#
|
5
|
+
# You can create list from entities or just their names:
|
6
|
+
#
|
7
|
+
# ```ruby
|
8
|
+
# Reality::List.new('Argentina', 'Ukraine')
|
9
|
+
# # => #<Reality::List[Argentina?, Ukraine?]>
|
10
|
+
# ```
|
11
|
+
#
|
12
|
+
# ...or from existing entities:
|
13
|
+
#
|
14
|
+
# ```ruby
|
15
|
+
# Reality::List.new(Reality::Entity.new('Argentina'), Reality::Entity.new('Ukraine'))
|
16
|
+
# # => #<Reality::List[Argentina?, Ukraine?]>
|
17
|
+
# ```
|
18
|
+
#
|
19
|
+
# List is useful for compact inspect (see above), and for effective
|
20
|
+
# batch loading (see {#load!}).
|
21
|
+
#
|
22
|
+
# Also, List is smart enough to remain List on multiple enumerable
|
23
|
+
# methods like `#select`, `#reject`, `#map` and so on:
|
24
|
+
#
|
25
|
+
# ```ruby
|
26
|
+
# Reality::List.new('Argentina', 'Bolivia', 'Chile').sample(2)
|
27
|
+
# # => #<Reality::List[Chile?, Argentina?]>
|
28
|
+
#
|
29
|
+
# Reality::List.new('Argentina', 'Bolivia', 'Chile').load!.map(&:capital)
|
30
|
+
# # => #<Reality::List[Buenos Aires?, La Paz?, Santiago?]>
|
31
|
+
# ```
|
32
|
+
#
|
2
33
|
class List < Array
|
3
34
|
using Refinements
|
4
|
-
|
35
|
+
|
36
|
+
# Creates List from a set of entity names or entity objects.
|
37
|
+
# Also aliased as `Reality::List()` method.
|
38
|
+
#
|
5
39
|
def initialize(*names)
|
6
40
|
super names.map(&method(:coerce))
|
7
41
|
end
|
8
42
|
|
43
|
+
# Loads all entities in batch. Optimized to make as few requests
|
44
|
+
# as possible. Typically, when you want to load several entities
|
45
|
+
#
|
46
|
+
# @return [self]
|
9
47
|
def load!
|
10
|
-
partition(&:wikidata_id).tap{|wd, wp|
|
48
|
+
compact.partition(&:wikidata_id).tap{|wd, wp|
|
11
49
|
load_by_wikipedia(wp)
|
12
50
|
load_by_wikidata(wd)
|
13
51
|
}
|
52
|
+
# try to fallback to labels:
|
53
|
+
compact.reject(&:loaded?).tap{|entities|
|
54
|
+
load_by_wikidata_labels(entities)
|
55
|
+
}
|
56
|
+
|
14
57
|
self
|
15
58
|
end
|
16
59
|
|
@@ -21,10 +64,24 @@ module Reality
|
|
21
64
|
}
|
22
65
|
end
|
23
66
|
|
67
|
+
# @return [String]
|
24
68
|
def inspect
|
25
|
-
"#<#{self.class.name}[#{map
|
69
|
+
"#<#{self.class.name}[#{map{|e| e ? e.to_s? : e.inspect}.join(', ')}]>"
|
26
70
|
end
|
27
71
|
|
72
|
+
# Prints compact description of the list. Implicitly loads all list
|
73
|
+
# if not loaded.
|
74
|
+
#
|
75
|
+
# ```ruby
|
76
|
+
# Reality::List.new('Argentina', 'Bolivia', 'Chile').describe
|
77
|
+
# # -------------------------
|
78
|
+
# # #<Reality::List(3 items)>
|
79
|
+
# # -------------------------
|
80
|
+
# # keys: adm_divisions (3), area (3), calling_code (3), capital (3), continent (3), coord (3), country (3), created_at (3), currency (3), gdp_nominal (3), gdp_ppp (3), head_of_government (2), head_of_state (3), highest_point (3), iso2_code (3), iso3_code (3), long_name (3), neighbours (3), official_website (1), organizations (3), part_of (3), population (3), tld (3), tz_offset (3)
|
81
|
+
# # types: country (3)
|
82
|
+
# ```
|
83
|
+
#
|
84
|
+
# @return [nil]
|
28
85
|
def describe
|
29
86
|
load! unless all?(&:loaded?)
|
30
87
|
|
@@ -50,7 +107,7 @@ module Reality
|
|
50
107
|
|
51
108
|
pages = Infoboxer.wp.get_h(*entities.map(&:name))
|
52
109
|
datum = Wikidata::Entity.
|
53
|
-
|
110
|
+
by_wikititle(*pages.values.compact.map(&:title))
|
54
111
|
|
55
112
|
entities.each do |entity|
|
56
113
|
page = pages[entity.name]
|
@@ -63,7 +120,7 @@ module Reality
|
|
63
120
|
return if entities.empty?
|
64
121
|
|
65
122
|
datum = Wikidata::Entity.
|
66
|
-
|
123
|
+
by_id(*entities.map(&:wikidata_id))
|
67
124
|
pages = Infoboxer.wp.
|
68
125
|
get_h(*datum.values.compact.map(&:en_wikipage).compact)
|
69
126
|
entities.each do |entity|
|
@@ -73,8 +130,19 @@ module Reality
|
|
73
130
|
end
|
74
131
|
end
|
75
132
|
|
133
|
+
def load_by_wikidata_labels(entities)
|
134
|
+
return if entities.empty?
|
135
|
+
|
136
|
+
datum = Wikidata::Entity.
|
137
|
+
by_label(*entities.map(&:name))
|
138
|
+
entities.each do |entity|
|
139
|
+
data = datum[entity.name]
|
140
|
+
entity.setup!(wikidata: data)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
76
144
|
def ensure_type(arr)
|
77
|
-
if arr.kind_of?(Array) && arr.all?{|e| e.is_a?(Entity)}
|
145
|
+
if arr.kind_of?(Array) && arr.all?{|e| e.nil? || e.is_a?(Entity)}
|
78
146
|
List[*arr]
|
79
147
|
else
|
80
148
|
arr
|
@@ -83,6 +151,8 @@ module Reality
|
|
83
151
|
|
84
152
|
def coerce(val)
|
85
153
|
case val
|
154
|
+
when nil
|
155
|
+
val
|
86
156
|
when String
|
87
157
|
Entity.new(val)
|
88
158
|
when Entity
|
data/lib/reality/measure.rb
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
module Reality
|
2
|
+
# Wrapper for numeric values.
|
3
|
+
# Example:
|
4
|
+
# Reality::Entity.new('Ukraine', load: true).area
|
5
|
+
# => #<Reality::Measure(603,500 km²)>
|
6
|
+
# Keeps information about value unit
|
7
|
+
# Allows coercion and general Numeric operations
|
2
8
|
class Measure
|
3
9
|
%w[unit].each{|mod| require_relative "measure/#{mod}"}
|
4
10
|
|
@@ -7,7 +13,9 @@ module Reality
|
|
7
13
|
def Measure.coerce(amount, unit)
|
8
14
|
amount && unit && new(amount, unit)
|
9
15
|
end
|
10
|
-
|
16
|
+
|
17
|
+
# @param amount - numeric value, e.g. 100.5
|
18
|
+
# @param unit - can be any string, e.g. 'km', '$'
|
11
19
|
def initialize(amount, unit)
|
12
20
|
@amount, @unit = Rational(amount), Unit.parse(unit)
|
13
21
|
end
|
@@ -18,10 +26,6 @@ module Reality
|
|
18
26
|
amount <=> other.amount
|
19
27
|
end
|
20
28
|
|
21
|
-
def ==(other)
|
22
|
-
amount == other.amount && unit == other.unit
|
23
|
-
end
|
24
|
-
|
25
29
|
def -@
|
26
30
|
self.class.new(-amount, unit)
|
27
31
|
end
|
@@ -65,12 +69,20 @@ module Reality
|
|
65
69
|
(num-1).times.inject(self){|res| res*self}
|
66
70
|
end
|
67
71
|
|
72
|
+
def abs
|
73
|
+
self.class.new(amount.abs, unit)
|
74
|
+
end
|
75
|
+
|
68
76
|
include Comparable
|
69
77
|
|
70
78
|
def to_s
|
71
79
|
'%s%s' % [Util::Format.number(amount), unit]
|
72
80
|
end
|
73
81
|
|
82
|
+
def to_h
|
83
|
+
{amount: amount.to_f, unit: unit.to_s}
|
84
|
+
end
|
85
|
+
|
74
86
|
def to_f
|
75
87
|
amount.to_f
|
76
88
|
end
|
@@ -91,8 +103,4 @@ module Reality
|
|
91
103
|
end
|
92
104
|
end
|
93
105
|
end
|
94
|
-
|
95
|
-
def Reality.Measure(*arg)
|
96
|
-
Measure.new(*arg)
|
97
|
-
end
|
98
106
|
end
|