reality 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,12 +2,16 @@ require 'forwardable'
2
2
 
3
3
  module Reality
4
4
  module Methods
5
- def Entity(name, entity_class = nil)
6
- Entity.load(name, entity_class)
5
+ def Entity(name)
6
+ Entity.load(name)
7
7
  end
8
8
 
9
9
  def List(*names)
10
- Entity::List.new(*names)
10
+ List.new(*names)
11
+ end
12
+
13
+ def Measure(*arg)
14
+ Measure.new(*arg)
11
15
  end
12
16
 
13
17
  extend Forwardable
@@ -0,0 +1,46 @@
1
+ module Reality
2
+ # This optional and higly experimental module allows treat ALL objects
3
+ # available with Reality, as Ruby constants (via redefined `const_missing`).
4
+ # This practice may seem questionable, so use it wisely!
5
+ #
6
+ # You can just use this module on its own:
7
+ #
8
+ # ```ruby
9
+ # Reality::Names::Argentina
10
+ # # => #<Reality::Entity(Argentina):country>
11
+ # ```
12
+ #
13
+ # ...Or just include it elsewhere:
14
+ #
15
+ # ```ruby
16
+ # include Reality::Names
17
+ #
18
+ # Argentina
19
+ # # => #<Reality::Entity(Argentina):country>
20
+ # ```
21
+ #
22
+ # Multi-word entities can also be called:
23
+ #
24
+ # ```ruby
25
+ # BuenosAires
26
+ # # => #<Reality::Entity(Buenos Aires):city>
27
+ # ```
28
+ #
29
+ # Though, more complicated entity names (with punctuations) can't be
30
+ # accessed this way.
31
+ #
32
+ module Names
33
+ def Names.const_missing(symbol)
34
+ name = symbol.to_s.
35
+ gsub('_', ' ').
36
+ gsub(/([a-z])([A-Z])/, '\1 \2')
37
+ Reality::Entity(name) or super
38
+ end
39
+
40
+ def Names.included(other)
41
+ other.define_singleton_method(:const_missing){|name|
42
+ Reality::Names.const_missing(name)
43
+ }
44
+ end
45
+ end
46
+ end
@@ -1,4 +1,5 @@
1
1
  module Reality
2
+ # @private
2
3
  module Refinements
3
4
  refine Object do
4
5
  def derp
@@ -1,11 +1,46 @@
1
1
  module Reality
2
+ # Simple class representing timezone offset (in minutes). Knows nothing
3
+ # about timezone name, DST or other complications, but useful when ONLY
4
+ # offset is known for some {Entity}.
5
+ #
6
+ # Usage:
7
+ #
8
+ # ```ruby
9
+ # # As entity property:
10
+ # Reality::Entity('Beijing').tz_offset
11
+ # # => #<Reality::TZOffset(UTC+08:00)>
12
+ #
13
+ # # On itself:
14
+ # o = Reality::TZOffset.parse('UTC+3')
15
+ # # => #<Reality::TZOffset(UTC+03:00)>
16
+ #
17
+ # o.now
18
+ # # => 2016-04-16 19:01:40 +0300
19
+ # o.local(2016, 4, 1, 20, 30)
20
+ # # => 2016-04-01 20:30:00 +0300
21
+ # o.convert(Time.now)
22
+ # # => 2016-04-16 19:02:22 +0300
23
+ # ```
24
+ #
2
25
  class TZOffset
3
26
  using Refinements
4
-
27
+
28
+ # Number of minutes in offset.
29
+ #
30
+ # @return [Fixnum]
5
31
  attr_reader :minutes
6
32
 
33
+ # @private
7
34
  MINUSES = /[−—–]/
8
35
 
36
+ # Parses TZOffset from string. Understands several options like:
37
+ #
38
+ # * `GMT` (not all TZ names, only those Ruby itself knows about);
39
+ # * `UTC+3` (or `GMT+3`);
40
+ # * `+03:30`;
41
+ # * ..and several combinations.
42
+ #
43
+ # @return [TZOffset]
9
44
  def self.parse(text)
10
45
  text = text.gsub(MINUSES, '-')
11
46
 
@@ -13,24 +48,32 @@ module Reality
13
48
  when /^[A-Z]{3}$/
14
49
  Time.zone_offset(text)
15
50
  when /^(?:UTC|GMT)?([+-]\d{1,2}:?\d{2})$/
16
- Time.zone_offset($1)
51
+ offset = $1
52
+ Time.zone_offset(offset.sub(/^([+-])(\d):/, '\10\2:'))
17
53
  when /^(?:UTC|GMT)?([+-]\d{1,2})/
18
54
  $1.to_i * 3600
19
55
  end.derp{|sec| sec && new(sec / 60)}
20
56
  end
21
-
57
+
58
+ # Constructs offset from number of minutes. In most cases, you don't
59
+ # want to use it, but rather {TZOffset.parse}.
60
+ #
61
+ # @param minutes [Fixnum] Number of minutes in offset.
22
62
  def initialize(minutes)
23
63
  @minutes = minutes
24
64
  end
25
65
 
66
+ # @return [String]
26
67
  def inspect
27
68
  '#<%s(UTC%+03i:%02i)>' % [self.class.name, *minutes.divmod(60)]
28
69
  end
29
70
 
71
+ # @return [String]
30
72
  def to_s
31
73
  '%+03i:%02i' % minutes.divmod(60)
32
74
  end
33
75
 
76
+ # @return [Boolean]
34
77
  def <=>(other)
35
78
  other.is_a?(TZOffset) or fail ArgumentError, "Can't compare TZOffset with #{other.class}"
36
79
  minutes <=> other.minutes
@@ -38,18 +81,28 @@ module Reality
38
81
 
39
82
  include Comparable
40
83
 
84
+ # Like Ruby's `Time.now`, but in desired offset.
85
+ #
86
+ # @return [Time] Current time in that offset.
41
87
  def now
42
88
  convert(Time.now)
43
89
  end
44
90
 
91
+ # Like Ruby's `Time.local`, but in desired offset.
92
+ #
93
+ # @return [Time] Constructed time in that offset.
45
94
  def local(*values)
46
95
  values << 0 until values.count == 6
47
96
  Time.new(*values, to_s)
48
97
  end
49
98
 
50
- # FIXME: usec are lost
99
+ # Converts `tm` into correct offset.
100
+ #
101
+ # @param tm [Time] Time object to convert (with any offset);
102
+ # @return [Time] Converted object.
51
103
  def convert(tm)
52
104
  pattern = tm.utc + minutes * 60
105
+ # FIXME: usec are lost
53
106
  Time.new(
54
107
  pattern.year,
55
108
  pattern.month,
@@ -1,4 +1,5 @@
1
1
  module Reality
2
+ # @private
2
3
  module Util
3
4
  module Format
4
5
  module_function
@@ -1,4 +1,5 @@
1
1
  module Reality
2
+ # @private
2
3
  module Util
3
4
  module Parse
4
5
  module_function
@@ -1,6 +1,6 @@
1
1
  module Reality
2
2
  MAJOR = 0
3
3
  MINOR = 0
4
- PATCH = 3
4
+ PATCH = 4
5
5
  VERSION = [MINOR, MAJOR, PATCH].join('.')
6
6
  end
@@ -1,6 +1,9 @@
1
+ require_relative 'wikidata/query'
2
+
1
3
  module Reality
2
4
  using Reality::Refinements
3
-
5
+
6
+ # @private
4
7
  module Wikidata
5
8
  class Link
6
9
  attr_reader :id, :label
@@ -18,198 +21,38 @@ module Reality
18
21
  end
19
22
  end
20
23
 
21
- # FIXME: I should be burn in hell for this mess. But it works. Somehow.
22
24
  class Entity
23
- PREFIX = %Q{
24
- PREFIX wikibase: <http://wikiba.se/ontology#>
25
- PREFIX wd: <http://www.wikidata.org/entity/>
26
- PREFIX wdt: <http://www.wikidata.org/prop/direct/>
27
- PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
28
- PREFIX p: <http://www.wikidata.org/prop/>
29
- PREFIX v: <http://www.wikidata.org/prop/statement/>
30
- PREFIX schema: <http://schema.org/>
31
- }
32
-
33
- SINGLE_QUERY = %Q{
34
- #{PREFIX}
35
-
36
- SELECT ?id ?p ?o ?oLabel WHERE {
37
- <https://en.wikipedia.org/wiki/%{title}> schema:about ?id .
38
- {
39
- ?id ?p ?o .
40
- FILTER(STRSTARTS(STR(?p), "http://www.wikidata.org/prop/direct/"))
41
- } union {
42
- ?id ?p ?o .
43
- filter(langMatches(lang(?o), "EN")).
44
- filter(?p = rdfs:label)
45
- }
46
- SERVICE wikibase:label {
47
- bd:serviceParam wikibase:language "en" .
48
- }
49
- }
50
- }
51
-
52
- ID_QUERY = %Q{
53
- #{PREFIX}
54
-
55
- SELECT ?id ?p ?o ?oLabel WHERE {
56
- bind(wd:%{id} as ?id)
57
- {
58
- ?id ?p ?o .
59
- FILTER(
60
- STRSTARTS(STR(?p), "http://www.wikidata.org/prop/direct/") ||
61
- (?p = rdfs:label && langMatches(lang(?o), "EN"))
62
- )
63
- } union {
64
- bind(schema:about as ?p) .
65
- ?o schema:about ?id .
66
- filter(strstarts(str(?o), "https://en.wikipedia.org/wiki/"))
67
- }
68
- SERVICE wikibase:label {
69
- bd:serviceParam wikibase:language "en" .
70
- }
71
- }
72
- }
73
-
74
- MULTIPLE_QUERY = %Q{
75
- #{PREFIX}
76
-
77
- SELECT ?id ?p ?o ?oLabel WHERE {
78
- %{selectors} .
79
- {
80
- ?id ?p ?o .
81
- FILTER(
82
- STRSTARTS(STR(?p), "http://www.wikidata.org/prop/direct/") ||
83
- (?p = rdfs:label && langMatches(lang(?o), "EN"))
84
- )
85
- } union {
86
- bind(schema:about as ?p) .
87
- ?o schema:about ?id .
88
- filter(strstarts(str(?o), "https://en.wikipedia.org/wiki/"))
89
- }
90
- SERVICE wikibase:label {
91
- bd:serviceParam wikibase:language "en" .
92
- }
93
- }
94
- }
95
- MULTIPLE_IDS_QUERY = %Q{
96
- #{PREFIX}
97
-
98
- SELECT ?id ?p ?o ?oLabel WHERE {
99
- %{selectors} .
100
- {
101
- ?id ?p ?o .
102
- FILTER(
103
- STRSTARTS(STR(?p), "http://www.wikidata.org/prop/direct/") ||
104
- (?p = rdfs:label && langMatches(lang(?o), "EN"))
105
- )
106
- } union {
107
- bind(schema:about as ?p) .
108
- ?o schema:about ?id .
109
- filter(strstarts(str(?o), "https://en.wikipedia.org/wiki/"))
110
- }
111
- SERVICE wikibase:label {
112
- bd:serviceParam wikibase:language "en" .
113
- }
114
- }
115
- }
116
- SELECTOR = %Q{
117
- {
118
- <https://en.wikipedia.org/wiki/%{title}> schema:about ?id
119
- }
120
- }
121
- IDSELECTOR = %Q{
122
- {
123
- BIND(wd:%{id} as ?id)
124
- }
125
- }
126
-
127
- UNSAFE = Regexp.union(URI::UNSAFE, /[,()']/)
128
-
129
25
  class << self
130
- def faraday
131
- @faraday ||= Faraday.new(url: 'https://query.wikidata.org/sparql'){|f|
132
- f.adapter Faraday.default_adapter
133
- }
26
+ def by_wikititle(*titles)
27
+ Query.by_wikititle(*titles)
134
28
  end
135
29
 
136
- def fetch(title)
137
- title = URI.escape(title, UNSAFE)
138
- faraday.get('', query: SINGLE_QUERY % {title: title}, format: :json).
139
- derp{|res| from_sparql(res.body, subject: 'id', predicate: 'p', object: 'o', object_label: 'oLabel')}
30
+ def by_id(*ids)
31
+ Query.by_id(*ids)
140
32
  end
141
33
 
142
- def fetch_by_id(id)
143
- faraday.get('', query: ID_QUERY % {id: id}, format: :json).
144
- derp{|res| from_sparql(res.body, subject: 'id', predicate: 'p', object: 'o', object_label: 'oLabel')}.
145
- first
34
+ def by_label(*labels)
35
+ Query.by_label(*labels)
146
36
  end
147
37
 
148
- WIKIURL = 'https://en.wikipedia.org/wiki/%{title}'
149
-
150
- MAX_SLICE = 20
151
-
152
- def fetch_list(*titles)
153
- titles.each_slice(MAX_SLICE).map{|titles_chunk|
154
- fetch_small_list(*titles_chunk)
155
- }.inject(:merge)
38
+ def one_by_wikititle(title)
39
+ by_wikititle(title).values.first
156
40
  end
157
41
 
158
- def fetch_list_by_id(*ids)
159
- ids.each_slice(MAX_SLICE).map{|ids_chunk|
160
- fetch_small_idlist(*ids_chunk)
161
- }.inject(:merge)
42
+ def one_by_id(id)
43
+ by_id(id).values.first
162
44
  end
163
45
 
164
- def fetch_small_list(*titles)
165
- titles.
166
- map{|t| SELECTOR % {title: URI.escape(t, UNSAFE)}}.
167
- join(' UNION ').
168
- derp{|selectors| MULTIPLE_QUERY % {selectors: selectors}}.
169
- derp{|query|
170
- faraday.get('', query: query, format: :json)
171
- }.
172
- derp{|res|
173
- from_sparql(
174
- res.body,
175
- subject: 'id',
176
- predicate: 'p',
177
- object: 'o',
178
- object_label: 'oLabel')
179
- }.
180
- map{|e|
181
- [e.en_wikipage, e]
182
- }.to_h
46
+ def one_by_label(label)
47
+ by_label(label).values.first
183
48
  end
184
49
 
185
-
186
- def fetch_small_idlist(*ids)
187
- ids.
188
- map{|i| IDSELECTOR % {id: i}}.
189
- join(' UNION ').
190
- derp{|selectors| MULTIPLE_IDS_QUERY % {selectors: selectors}}.
191
- derp{|query|
192
- faraday.get('', query: query, format: :json)
193
- }.
194
- derp{|res|
195
- from_sparql(
196
- res.body,
197
- subject: 'id',
198
- predicate: 'p',
199
- object: 'o',
200
- object_label: 'oLabel')
201
- }.
202
- map{|e|
203
- [e.id, e]
204
- }.to_h
205
- end
206
-
207
- def from_sparql(sparql_json, subject: 'subject', predicate: 'predicate', object: 'object', object_label: 'object_label')
50
+ def from_sparql(sparql_json)
208
51
  JSON.parse(sparql_json)['results']['bindings'].map{|row|
209
52
  [
210
- row[subject]['value'].sub('http://www.wikidata.org/entity/', ''),
211
- row[predicate]['value'].sub('http://www.wikidata.org/prop/direct/', ''),
212
- row[object].merge('label' => row[object_label]['value'])
53
+ row['s']['value'].sub('http://www.wikidata.org/entity/', ''),
54
+ row['p']['value'].sub('http://www.wikidata.org/prop/direct/', ''),
55
+ row['o'].merge('label' => row['oLabel']['value'])
213
56
  ]
214
57
  }.group_by(&:first).
215
58
  map{|id, rows|
@@ -223,6 +66,7 @@ module Reality
223
66
  to_h
224
67
  end
225
68
 
69
+ # FIXME: move all parse_* to util/parsers or wikidata/parsers
226
70
  def parse_value(hash)
227
71
  case hash['type']
228
72
  when 'literal'
@@ -278,6 +122,10 @@ module Reality
278
122
  @predicates[pred]
279
123
  end
280
124
 
125
+ def id_i
126
+ id.sub('Q', '').to_i
127
+ end
128
+
281
129
  def label
282
130
  self['http://www.w3.org/2000/01/rdf-schema#label'].first
283
131
  end