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.
@@ -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