reality 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.dokaz +1 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +538 -66
  5. data/bin/reality +9 -0
  6. data/config/demo.yml +3 -0
  7. data/data/wikidata-predicates.json +1 -0
  8. data/data/wikidata-predicates.yaml +2089 -0
  9. data/lib/reality.rb +26 -7
  10. data/lib/reality/config.rb +46 -0
  11. data/lib/reality/definitions/dictionaries.rb +67 -0
  12. data/lib/reality/definitions/helpers.rb +34 -0
  13. data/lib/reality/definitions/wikidata.rb +105 -0
  14. data/lib/reality/definitions/wikipedia_character.rb +17 -0
  15. data/lib/reality/definitions/wikipedia_city.rb +19 -0
  16. data/lib/reality/definitions/wikipedia_continent.rb +21 -0
  17. data/lib/reality/definitions/wikipedia_country.rb +23 -0
  18. data/lib/reality/definitions/wikipedia_musical_artist.rb +15 -0
  19. data/lib/reality/definitions/wikipedia_person.rb +17 -0
  20. data/lib/reality/entity.rb +152 -0
  21. data/lib/reality/entity/coercion.rb +76 -0
  22. data/lib/reality/entity/wikidata_predicates.rb +31 -0
  23. data/lib/reality/entity/wikipedia_type.rb +73 -0
  24. data/lib/reality/extras/geonames.rb +29 -0
  25. data/lib/reality/extras/open_weather_map.rb +63 -0
  26. data/lib/reality/geo.rb +122 -0
  27. data/lib/reality/infoboxer_templates.rb +8 -0
  28. data/lib/reality/list.rb +95 -0
  29. data/lib/reality/measure.rb +18 -12
  30. data/lib/reality/measure/unit.rb +5 -1
  31. data/lib/reality/methods.rb +16 -0
  32. data/lib/reality/pretty_inspect.rb +11 -0
  33. data/lib/reality/refinements.rb +26 -0
  34. data/lib/reality/shortcuts.rb +11 -0
  35. data/lib/reality/tz_offset.rb +64 -0
  36. data/lib/reality/util/formatters.rb +35 -0
  37. data/lib/reality/util/parsers.rb +53 -0
  38. data/lib/reality/version.rb +6 -0
  39. data/lib/reality/wikidata.rb +310 -0
  40. data/reality.gemspec +12 -3
  41. data/script/extract_wikidata_properties.rb +23 -0
  42. data/script/lib/nokogiri_more.rb +175 -0
  43. metadata +137 -7
  44. data/examples/all_countries.rb +0 -16
  45. data/lib/reality/country.rb +0 -283
@@ -1,15 +1,34 @@
1
1
  require 'infoboxer'
2
+ require 'yaml'
2
3
 
3
4
  module Reality
4
- require_relative 'reality/infoboxer_templates'
5
-
6
- # basic functionality
7
- %w[measure].each do |mod|
8
- require_relative "reality/#{mod}"
5
+ def self.require_(*modules)
6
+ modules.flatten.flat_map{|pattern|
7
+ Dir[File.expand_path("../reality/#{pattern}.rb", __FILE__)]
8
+ }.each(&Kernel.method(:require))
9
9
  end
10
10
 
11
+ # basic functionality
12
+ require_ %w[version refinements config measure geo tz_offset]
13
+ require_ %w[util/parsers util/formatters]
14
+
15
+ # engines
16
+ require_ %w[infoboxer_templates wikidata]
17
+ Infoboxer.user_agent = "Reality/#{VERSION} (https://github.com/molybdenum-99/reality; zverok.offline@gmail.com)"
18
+
11
19
  # entities
12
- %w[country].each do |mod|
13
- require_relative "reality/#{mod}"
20
+ require_ %w[entity list]
21
+ require_ %w[definitions/*]
22
+ require_ %w[methods]
23
+
24
+ extend Methods
25
+
26
+ def self.reload!
27
+ require_ %w[definitions/*]
14
28
  end
29
+
30
+ # extras
31
+ require_ %w[extras/open_weather_map extras/geonames]
32
+ include Extras::OpenWeatherMap
33
+ include Extras::Geonames
15
34
  end
@@ -0,0 +1,46 @@
1
+ require 'hashie'
2
+
3
+ module Reality
4
+ class Config
5
+ attr_reader :keys, :data
6
+
7
+ def initialize
8
+ @keys = {}
9
+ @data = {}.extend Hashie::Extensions::DeepFetch
10
+ end
11
+
12
+ def load(str)
13
+ if File.exists?(str)
14
+ str = File.read(str)
15
+ end
16
+
17
+ @data.update(YAML.load(str))
18
+ end
19
+
20
+ def fetch(*path)
21
+ data.deep_fetch(*path){
22
+ if (known = @keys[path])
23
+ fail KeyError, "Expected #{path.join('.')} to exist in config. It is #{known[:desc]}"
24
+ else
25
+ fail KeyError, "Expected #{path.join('.')} to exist in config."
26
+ end
27
+ }
28
+ end
29
+
30
+ def register(*path, **opts)
31
+ @keys[path] = opts
32
+ end
33
+ end
34
+
35
+ def Reality.config
36
+ @config ||= Config.new
37
+ end
38
+
39
+ def Reality.configure(cfg)
40
+ if cfg == :demo
41
+ config.load(File.expand_path('../../../config/demo.yml', __FILE__))
42
+ else
43
+ config.load(cfg)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,67 @@
1
+ module Reality
2
+ module Dictionaries
3
+ module_function
4
+
5
+ using Reality::Refinements
6
+
7
+ def countries
8
+ List.new(*countries_by_continents_cache.keys)
9
+ end
10
+
11
+ def continents
12
+ @continents ||=
13
+ Infoboxer.wp.get('Continent').
14
+ sections('Area and population').tables.first.
15
+ lookup(:TableHeading, index: 0).lookup(:Wikilink).
16
+ derp{|links| Entity::Coercion.coerce(links, [:entity])}
17
+ end
18
+
19
+ CITY_SYNONYMS = [
20
+ 'City',
21
+ 'Municipality',
22
+ 'Commune', # France
23
+ 'GCCSA/SUA' # Australia
24
+ ]
25
+
26
+ CITIES_PAGE_BY_COUNTRY =
27
+ Hash.new{|_, name| 'List of cities in %s' % name}.
28
+ merge(
29
+ 'United States' => 'List of United States cities by population',
30
+ 'Australia' => 'List of cities in Australia by population'
31
+ )
32
+
33
+ def cities_by_country(name)
34
+ page = Infoboxer.wp.get(CITIES_PAGE_BY_COUNTRY[name]) or
35
+ return Entity::List.new()
36
+
37
+ page.tables.map{|t|
38
+ t.heading_row or next []
39
+
40
+ idx = t.heading_row.children.
41
+ index{|c| c.text =~ /\bname\b/i || CITY_SYNONYMS.any?{|s| c.text.strip.start_with?(s)}} or next []
42
+
43
+ t.lookup(:TableCell, index: idx).lookup(:Wikilink)
44
+ }.flatten.
45
+ derp{|links| Entity::Coercion.coerce(links, [:entity])}
46
+ end
47
+
48
+ def countries_by_continent(name)
49
+ countries_by_continents_cache.select{|k, v| v == name}.map(&:first).
50
+ derp{|names| List.new(*names)}
51
+ end
52
+
53
+ def countries_by_continents_cache
54
+ @by_continents ||= Infoboxer.wp.
55
+ get('List of countries by continent').
56
+ sections.first.
57
+ sections.map{|s|
58
+ continent = s.heading.text_
59
+ s.tables.first.
60
+ lookup(:Wikilink, :bold?).map(&:link).
61
+ map{|country| [country, continent]}
62
+ }.flatten(1).
63
+ reject{|country, continent| country == 'Holy See'}. # it has [Vatican City]/[Holy See]
64
+ to_h
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,34 @@
1
+ require 'time_boots'
2
+
3
+ module Reality
4
+ # Just assorted "cool things", included into all entities
5
+ # Subject to change/refactor
6
+ module Helpers
7
+ def alive?
8
+ !birthday.nil? && date_of_death.nil?
9
+ end
10
+
11
+ def dead?
12
+ !date_of_death.nil?
13
+ end
14
+
15
+ def age_at(tm)
16
+ # TimeBoots fail with Time vs Date :(
17
+ #birthday && TimeBoots.year.measure(birthday, tm)
18
+
19
+ from = birthday || created_at || published_at
20
+
21
+ if from.month < tm.month || from.month == tm.month && from.day <= tm.day
22
+ tm.year - from.year
23
+ else
24
+ tm.year - from.year - 1
25
+ end
26
+ end
27
+
28
+ def age
29
+ age_at(Date.today)
30
+ end
31
+ end
32
+
33
+ Reality::Entity.include Helpers
34
+ end
@@ -0,0 +1,105 @@
1
+ module Reality
2
+ Entity::WikidataPredicates.define do
3
+ # Generic relations ------------------------------------------------
4
+ predicate 'P361', :part_of, [:entity]
5
+ predicate 'P527', :parts, [:entity] # aliases: :members
6
+
7
+ predicate 'P155', :follows, :entity
8
+ predicate 'P156', :precedes, :entity
9
+ predicate 'P571', :created_at, :date # TODO: aliases: :founded_at, :incepted_at
10
+
11
+ predicate 'P740', :location, :entity
12
+
13
+ predicate 'P737', :influenced_by, [:entity]
14
+
15
+ # Object features --------------------------------------------------
16
+ predicate 'P2048', :height, :measure, unit: 'm'
17
+
18
+ # Geography --------------------------------------------------------
19
+ predicate 'P625', :coord, :coord
20
+
21
+ predicate 'P30' , :continent, :entity
22
+ predicate 'P17' , :country, :entity
23
+ predicate 'P610', :highest_point, :entity
24
+
25
+ predicate 'P36' , :capital, :entity
26
+ predicate 'P150', :adm_divisions, [:entity]
27
+
28
+ predicate 'P47' , :neighbours, [:entity]
29
+
30
+ predicate 'P2046', :area, :measure, unit: 'km²'
31
+ predicate 'P2044', :elevation, :measure, unit: 'm'
32
+
33
+ # Economy and socilogy ---------------------------------------------
34
+ predicate 'P38', :currency, :entity
35
+ predicate 'P463', :organizations, [:entity]
36
+ predicate 'P2131', :gdp_nominal, :measure, unit: '$'
37
+ predicate 'P1082',:population, :measure, unit: 'person'
38
+
39
+ predicate 'P35', :head_of_state, :entity
40
+
41
+ # References -------------------------------------------------------
42
+ predicate 'P297', :iso2_code, :string
43
+ predicate 'P298', :iso3_code, :string
44
+ predicate 'P78', :tld, :string
45
+ predicate 'P474', :calling_code, :string
46
+ predicate 'P421', :tz_offset, :tz_offset
47
+
48
+ # People -----------------------------------------------------------
49
+ # personal
50
+ predicate 'P19', :birth_place, :entity
51
+ predicate 'P569', :birthday, :date
52
+ predicate 'P570', :date_of_death, :date
53
+ predicate 'P20', :place_of_death, :entity
54
+ predicate 'P21', :sex, :string
55
+ predicate 'P735', :given_name, :string
56
+
57
+ # family
58
+ predicate 'P26', :spouse, :entity
59
+ predicate 'P40', :children, [:entity]
60
+ predicate 'P22', :father, :entity
61
+
62
+ # social
63
+ predicate 'P551', :residence, :entity
64
+ predicate 'P27', :citizenship, :entity
65
+ predicate 'P39', :position, :string
66
+ predicate 'P106', :occupations, [:string]
67
+
68
+ # General creative works & workers ---------------------------------
69
+ predicate 'P577', :published_at, :date
70
+ predicate 'P136', :genres, [:string]
71
+ predicate 'P166', :awards, [:entity]
72
+ predicate 'P1411', :nominations, [:entity]
73
+ predicate 'P921', :work_subjects, [:string]
74
+ predicate 'P364', :original_languages, [:string]
75
+
76
+ # Music album ------------------------------------------------------
77
+ predicate 'P658', :tracks, [:string]
78
+ predicate 'P175', :performer, :entity
79
+ #predicate 'P175', :performers, [:entity] - TODO
80
+
81
+ # Companies --------------------------------------------------------
82
+ predicate 'P112', :founders, [:entity]
83
+ predicate 'P127', :owners, [:entity]
84
+ predicate 'P169', :ceo, :entity
85
+ predicate 'P1128', :employees_count, :measure, unit: 'person'
86
+ predicate 'P452', :industry, :string
87
+
88
+ # Software ---------------------------------------------------------
89
+ predicate 'P178', :developers, [:entity]
90
+ predicate 'P275', :licenses, [:string]
91
+ predicate 'P348', :version, :string
92
+
93
+ # Movies -----------------------------------------------------------
94
+ predicate 'P57', :directors, [:entity]
95
+ predicate 'P162', :producers, [:entity]
96
+ predicate 'P161', :actors, [:entity]
97
+
98
+ # Wehicles ---------------------------------------------------------
99
+ predicate 'P1029', :crew_members, [:entity]
100
+
101
+ # Fictional entities -----------------------------------------------
102
+ predicate 'P1080', :fictional_universe, :string
103
+ predicate 'P1441', :present_in_works, [:entity]
104
+ end
105
+ end
@@ -0,0 +1,17 @@
1
+ module Reality
2
+ module Character
3
+ extend Entity::WikipediaType
4
+
5
+ infobox_name 'Infobox character'
6
+
7
+ # FIXME: Both should be auto-parsed
8
+ infobox 'species', :species, :entity,
9
+ parse: ->(var){var.lookup(:Wikilink).first}
10
+
11
+ infobox 'affiliation', :affiliations, [:entity],
12
+ parse: ->(var){var.lookup(:Wikilink)}
13
+
14
+ infobox 'portrayer', :portrayer, :entity,
15
+ parse: ->(var){var.lookup(:Wikilink).first}
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Reality
2
+ module City
3
+ extend Entity::WikipediaType
4
+
5
+ infobox_name 'Infobox settlement',
6
+ 'Infobox city Japan',
7
+ 'Infobox Russian city', 'Infobox Russian town',
8
+ 'Infobox Russian inhabited locality',
9
+ 'Infobox Russian federal subject'
10
+
11
+ infobox 'name', :long_name, :string
12
+
13
+ infobox 'area_km2', :area, :measure, unit: 'km²'
14
+ infobox 'area_total_km2', :area, :measure, unit: 'km²'
15
+
16
+ infobox 'population_total', :population, :measure, unit: 'person'
17
+ infobox 'population_metro', :population_metro, :measure, unit: 'person'
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module Reality
2
+ module Continent
3
+ extend Entity::WikipediaType
4
+
5
+ infobox_name 'Infobox continent', 'Infobox Continent' # FIXME: Infoboxer is a bit dumb about this
6
+
7
+ infobox 'area', :area, :measure, unit: 'km²',
8
+ parse: ->(var){
9
+ str = var.children.
10
+ templates(name: /^Convert$/i). # Infoboxer is dumb here, tooo :(
11
+ fetch('1').text
12
+
13
+ str = var.children.text if str.empty?
14
+ Util::Parse.scaled_number(str)
15
+ }
16
+
17
+ def countries
18
+ @countries ||= Dictionaries.countries_by_continent(name)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module Reality
2
+ module Country
3
+ extend Entity::WikipediaType
4
+
5
+ infobox_name 'Infobox country'
6
+
7
+ infobox 'conventional_long_name', :long_name, :string
8
+ infobox 'area_km2', :area, :measure, unit: 'km²'
9
+
10
+ infobox 'GDP_PPP', :gdp_ppp, :measure, unit: '$',
11
+ parse: ->(var){
12
+ str = var.text.strip.sub(/^((Int|US)?\$|USD)/, '')
13
+ Util::Parse.scaled_number(str)
14
+ }
15
+
16
+ infobox 'population_census', :population, :measure, unit: 'person'
17
+ infobox 'population_estimate', :population, :measure, unit: 'person'
18
+
19
+ def cities
20
+ @cities ||= Dictionaries.cities_by_country(name)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ module Reality
2
+ module MusicalArtist
3
+ extend Entity::WikipediaType
4
+
5
+ infobox_name 'Infobox musical artist'
6
+
7
+ parse :albums, [:entity] do |page|
8
+ if (sec = page.sections('Discography').first)
9
+ sec.lookup(:UnorderedList).first.lookup(:Wikilink, :italic?)
10
+ else
11
+ nil
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Reality
2
+ module Person
3
+ extend Entity::WikipediaType
4
+
5
+ infobox_name 'Infobox person'
6
+
7
+ # Singular artists, like Bjork or David Bowie
8
+ # TODO: should not repeat code from musical artist, but include it from module?
9
+ parse :albums, [:entity] do |page|
10
+ if (sec = page.sections('Discography').first)
11
+ sec.lookup(:UnorderedList).first.lookup(:Wikilink, :italic?)
12
+ else
13
+ nil
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,152 @@
1
+ module Reality
2
+ require_ %w[entity/coercion entity/wikidata_predicates entity/wikipedia_type]
3
+
4
+ class Entity
5
+ using Refinements
6
+
7
+ attr_reader :wikipage, :wikidata, :wikidata_id
8
+ attr_reader :values, :wikipedia_type
9
+
10
+ def initialize(name, wikipage: nil, wikidata: nil, wikidata_id: nil, load: false)
11
+ @name = name
12
+ @wikipage, @wikidata, @wikidata_id = wikipage, wikidata, wikidata_id
13
+ @values = {}
14
+
15
+ load! if load
16
+ after_load if @wikipage
17
+ end
18
+
19
+ def name
20
+ @wikipage ? @wikipage.title : @name
21
+ end
22
+
23
+ def inspect
24
+ if @wikipedia_type && @wikipedia_type.symbol
25
+ "#<#{self.class}#{loaded? ? '' : '?'}(#{name}):#{@wikipedia_type.symbol}>"
26
+ else
27
+ "#<#{self.class}#{loaded? ? '' : '?'}(#{name})>"
28
+ end
29
+ end
30
+
31
+ def _describe
32
+ load! unless loaded?
33
+ Util::Format.describe(inspect, values.map{|k,v| [k, v.inspect]})
34
+ end
35
+
36
+ def describe
37
+ puts _describe
38
+ nil
39
+ end
40
+
41
+ def to_s
42
+ name
43
+ end
44
+
45
+ def to_s?
46
+ # FIXME: fuuuuuuuu
47
+ "#{name.include?(',') ? '"' + name + '"' : name}#{loaded? ? '' : '?'}"
48
+ end
49
+
50
+ def load!
51
+ if @wikidata_id
52
+ @wikidata = Wikidata::Entity.fetch_by_id(@wikidata_id)
53
+ if @wikidata && @wikidata.en_wikipage
54
+ @wikipage = Infoboxer.wikipedia.get(@wikidata.en_wikipage)
55
+ end
56
+ else
57
+ @wikipage = Infoboxer.wikipedia.get(name)
58
+ if @wikipage
59
+ @wikidata = Wikidata::Entity.fetch(@wikipage.title).first
60
+ end
61
+ end
62
+ after_load
63
+ self
64
+ end
65
+
66
+ def setup!(wikipage: nil, wikidata: nil)
67
+ @wikipage, @wikidata = wikipage, wikidata
68
+ after_load if @wikipage
69
+ end
70
+
71
+ def loaded?
72
+ !!@wikipage
73
+ end
74
+
75
+ # Don't try to convert me!
76
+ UNSUPPORTED_METHODS = [:to_hash, :to_ary, :to_a, :to_str, :to_int]
77
+
78
+ def method_missing(sym, *arg, **opts, &block)
79
+ if arg.empty? && opts.empty? && !block && sym !~ /[=?!]/ &&
80
+ !UNSUPPORTED_METHODS.include?(sym)
81
+
82
+ load! unless loaded?
83
+
84
+ # now some new method COULD emerge while loading
85
+ if methods.include?(sym)
86
+ send(sym)
87
+ else
88
+ values[sym]
89
+ end
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ def respond_to?(sym)
96
+ sym !~ /[=?!]/ && !UNSUPPORTED_METHODS.include?(sym) || super
97
+ end
98
+
99
+ class << self
100
+ def load(name, type = nil)
101
+ Entity.new(name, load: true).tap{|entity|
102
+ return nil if entity.instance_variable_get('@wikipage').nil?
103
+ return nil if type && entity.wikipedia_type != type
104
+ }
105
+ end
106
+ end
107
+
108
+ #def to_h
109
+ #if respond_to?(:properties)
110
+ #properties.map{|sym|
111
+ #[sym, to_simple_type(self.send(sym))]
112
+ #}.to_h
113
+ #else
114
+ #{}
115
+ #end
116
+ #end
117
+
118
+ protected
119
+
120
+ def after_load
121
+ if @wikipage && !@wikipedia_type
122
+ if (@wikipedia_type = WikipediaType.for(self))
123
+ extend(@wikipedia_type)
124
+ end
125
+ end
126
+ if @wikidata
127
+ @values.update(WikidataPredicates.parse(@wikidata))
128
+ end
129
+ (@values.keys - methods).each do |sym|
130
+ define_singleton_method(sym){@values[sym]}
131
+ end
132
+ end
133
+
134
+ def to_simple_type(val)
135
+ case val
136
+ when nil, Numeric, String, Symbol
137
+ val
138
+ when Array
139
+ val.map{|v| to_simple_type(v)}
140
+ when Hash
141
+ val.map{|k, v| [to_simple_type(k), to_simple_type(v)]}.to_h
142
+ when Entity
143
+ val.to_s
144
+ when Reality::Measure
145
+ val.amount.to_i
146
+ else
147
+ fail ArgumentError, "Non-coercible value #{val.class}"
148
+ end
149
+ end
150
+
151
+ end
152
+ end