reality 0.0.3 → 0.0.4
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/.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
|