reality 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,13 @@
1
1
  #!/usr/bin/env ruby
2
+
2
3
  require 'rubygems'
3
4
  require 'reality'
4
- require 'reality/pretty_inspect'
5
- require 'reality/shortcuts'
5
+ require 'reality/command_line'
6
6
 
7
- Reality.configure :demo
7
+ reality_runner = Reality::CommandLine.new
8
8
 
9
- class << self
10
- # including it ONLY into the main object, not Kernel
11
- include Reality::Methods
9
+ if reality_runner.interactive?
10
+ reality_runner.run_interactive_shell
11
+ else
12
+ puts reality_runner.results
12
13
  end
13
-
14
- require 'irb'
15
- ARGV.shift until ARGV.empty?
16
- IRB.start
@@ -1,3 +1,4 @@
1
1
  keys:
2
2
  open_weather_map: '90d73c1188829195d023b5a5fc6399e1'
3
3
  geonames: 'realitygeo'
4
+ quandl: JU6KEPEkqqqfVkcpa9MU
@@ -1,7 +1,14 @@
1
1
  require 'infoboxer'
2
2
  require 'yaml'
3
3
 
4
+ # Reality is library for accessing all world data, starting from Wikipedia.
5
+ #
6
+ # Look at {Entity} for good starting point.
7
+ #
8
+ # You also may want to navigate [Getting started](https://github.com/molybdenum-99/reality/wiki/Getting-started)
9
+ # page in our wiki.
4
10
  module Reality
11
+ # @private
5
12
  def self.require_(*modules)
6
13
  modules.flatten.flat_map{|pattern|
7
14
  Dir[File.expand_path("../reality/#{pattern}.rb", __FILE__)]
@@ -19,16 +26,14 @@ module Reality
19
26
  # entities
20
27
  require_ %w[entity list]
21
28
  require_ %w[definitions/*]
22
- require_ %w[methods]
29
+ require_ %w[methods names]
23
30
 
31
+ include Methods
24
32
  extend Methods
25
33
 
26
- def self.reload!
27
- require_ %w[definitions/*]
28
- end
29
-
30
34
  # extras
31
- require_ %w[extras/open_weather_map extras/geonames]
35
+ require_ %w[extras/open_weather_map extras/geonames extras/quandl]
32
36
  include Extras::OpenWeatherMap
33
37
  include Extras::Geonames
38
+ include Extras::Quandl
34
39
  end
@@ -0,0 +1,124 @@
1
+ require 'optparse'
2
+ require 'reality/pretty_inspect'
3
+
4
+ module Reality
5
+ # This module contains services used from `bin/reality`, you typically
6
+ # don't need to think of it.
7
+ class CommandLine
8
+ attr_accessor :search_term, :article, :commands, :options, :errors
9
+
10
+ def initialize
11
+ parse_arguments
12
+ end
13
+
14
+ def self.display_usage
15
+ puts "usage: reality \"[search-string]\" [entity-command] [chained-command(s) ...]"
16
+ end
17
+
18
+ def interactive?
19
+ !!self.options[:interactive]
20
+ end
21
+
22
+ def article_found?
23
+ !self.article.nil?
24
+ end
25
+
26
+ def subcommand_for(object, subcommand)
27
+ if object.respond_to?(subcommand.to_sym)
28
+ puts "Calling #{subcommand} on #{object.inspect}" if ENV['DEBUG']
29
+ object.send(subcommand.to_sym)
30
+ else
31
+ self.errors << "#{object} doesn't respond to #{subcommand}"
32
+ nil
33
+ end
34
+ end
35
+
36
+ def results
37
+ return "Nothing found for: #{self.search_term}" unless self.article_found?
38
+ puts "Attempting entity chain: #{self.commands} on #{self.article.inspect}" if ENV['DEBUG']
39
+
40
+ result = subcommand_for(self.article, self.commands.shift)
41
+
42
+ while subcommand = self.commands.shift
43
+ result = subcommand_for(result, subcommand)
44
+ end
45
+
46
+ if self.errors.any?
47
+ "Error: #{ self.errors.join("\n") }"
48
+ else
49
+ result
50
+ end
51
+ end
52
+
53
+ def run_interactive_shell
54
+ require 'irb'
55
+ require 'reality/shortcuts'
56
+ ::ARGV.clear
57
+
58
+ # FIXME: can't see better means to have everything accessible & included
59
+ #IRB::ExtendCommandBundle.include(Reality)
60
+ TOPLEVEL_BINDING.receiver.send(:include, Reality)
61
+
62
+ IRB.setup nil
63
+
64
+ IRB.conf[:IRB_NAME] = 'reality'
65
+
66
+ IRB.conf[:PROMPT] = {}
67
+ IRB.conf[:PROMPT][:REALITY] = {
68
+ :PROMPT_I => '%N:%03n:%i> ',
69
+ :PROMPT_S => '%N:%03n:%i%l ',
70
+ :PROMPT_C => '%N:%03n:%i* ',
71
+ :RETURN => "# => %s\n"
72
+ }
73
+ IRB.conf[:PROMPT_MODE] = :REALITY
74
+ IRB.conf[:RC] = false
75
+
76
+ IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
77
+
78
+ require 'irb/ext/multi-irb'
79
+ IRB.irb nil, TOPLEVEL_BINDING
80
+ end
81
+
82
+ private
83
+
84
+ def parse_options
85
+ self.options = {}
86
+
87
+ OptionParser.new do |opts|
88
+ opts.banner = "Usage: reality [options] [search-string] [entity-command] [chained-command(s) ...]"
89
+
90
+ opts.on_tail("-i", "--interactive", "Run an interactive IRB session with Reality pre-configured for querying.") do
91
+ self.options[:interactive] = true
92
+ end
93
+
94
+ opts.on_tail("-h", "--help", "Show this message") do
95
+ puts opts
96
+ exit
97
+ end
98
+ end.parse!
99
+ end
100
+
101
+ def parse_arguments
102
+ parse_options
103
+ self.search_term = "#{::ARGV.shift}"
104
+
105
+ if self.options[:interactive]
106
+ Reality.configure :demo
107
+ elsif self.search_term.length > 0
108
+ self.article = Reality::Entity(self.search_term)
109
+ self.errors = []
110
+
111
+ if ::ARGV.count > 0
112
+ self.commands = ::ARGV.map(&:to_sym)
113
+ else
114
+ self.commands = [:describe]
115
+ end
116
+
117
+ Reality.configure :demo
118
+ else
119
+ self.class.display_usage
120
+ exit
121
+ end
122
+ end
123
+ end
124
+ end
@@ -32,15 +32,22 @@ module Reality
32
32
  end
33
33
  end
34
34
 
35
+ # @private
35
36
  def Reality.config
36
37
  @config ||= Config.new
37
38
  end
38
39
 
39
- def Reality.configure(cfg)
40
- if cfg == :demo
40
+ # Allows to configure Reality.
41
+ #
42
+ # @param path [String] Path to config. See `config/demo.yml` for config
43
+ # sample. Also, you can use `:demo` value for config Reality with
44
+ # demo keys.
45
+ #
46
+ def Reality.configure(path)
47
+ if path == :demo
41
48
  config.load(File.expand_path('../../../config/demo.yml', __FILE__))
42
49
  else
43
- config.load(cfg)
50
+ config.load(path)
44
51
  end
45
52
  end
46
53
  end
@@ -1,6 +1,7 @@
1
1
  require 'time_boots'
2
2
 
3
3
  module Reality
4
+ # @private
4
5
  # Just assorted "cool things", included into all entities
5
6
  # Subject to change/refactor
6
7
  module Helpers
@@ -9,6 +9,7 @@ module Reality
9
9
  predicate 'P571', :created_at, :date # TODO: aliases: :founded_at, :incepted_at
10
10
 
11
11
  predicate 'P740', :location, :entity
12
+ predicate 'P585', :date , :date # TODO: maybe :datetime?
12
13
 
13
14
  predicate 'P737', :influenced_by, [:entity]
14
15
 
@@ -30,13 +31,17 @@ module Reality
30
31
  predicate 'P2046', :area, :measure, unit: 'km²'
31
32
  predicate 'P2044', :elevation, :measure, unit: 'm'
32
33
 
33
- # Economy and socilogy ---------------------------------------------
34
+ predicate 'P969', :street_address, :string
35
+ predicate 'P131', :located_in, :entity
36
+
37
+ # Economy and sociology ---------------------------------------------
34
38
  predicate 'P38', :currency, :entity
35
39
  predicate 'P463', :organizations, [:entity]
36
40
  predicate 'P2131', :gdp_nominal, :measure, unit: '$'
37
41
  predicate 'P1082',:population, :measure, unit: 'person'
38
42
 
39
43
  predicate 'P35', :head_of_state, :entity
44
+ predicate 'P6', :head_of_government, :entity
40
45
 
41
46
  # References -------------------------------------------------------
42
47
  predicate 'P297', :iso2_code, :string
@@ -101,5 +106,9 @@ module Reality
101
106
  # Fictional entities -----------------------------------------------
102
107
  predicate 'P1080', :fictional_universe, :string
103
108
  predicate 'P1441', :present_in_works, [:entity]
109
+
110
+ # Internet ---------------------------------------------------------
111
+ predicate 'P856', :official_website, :string
112
+ predicate 'P2002', :twitter_username, :string
104
113
  end
105
114
  end
@@ -1,4 +1,5 @@
1
1
  module Reality
2
+ # @private
2
3
  module Character
3
4
  extend Entity::WikipediaType
4
5
 
@@ -1,4 +1,5 @@
1
1
  module Reality
2
+ # @private
2
3
  module City
3
4
  extend Entity::WikipediaType
4
5
 
@@ -1,4 +1,5 @@
1
1
  module Reality
2
+ # @private
2
3
  module Continent
3
4
  extend Entity::WikipediaType
4
5
 
@@ -1,4 +1,5 @@
1
1
  module Reality
2
+ # @private
2
3
  module Country
3
4
  extend Entity::WikipediaType
4
5
 
@@ -1,4 +1,5 @@
1
1
  module Reality
2
+ # @private
2
3
  module MusicalArtist
3
4
  extend Entity::WikipediaType
4
5
 
@@ -1,4 +1,5 @@
1
1
  module Reality
2
+ # @private
2
3
  module Person
3
4
  extend Entity::WikipediaType
4
5
 
@@ -1,25 +1,120 @@
1
1
  module Reality
2
2
  require_ %w[entity/coercion entity/wikidata_predicates entity/wikipedia_type]
3
-
3
+
4
+ # Reality::Entity is a main concept of the library. It represents errr
5
+ # well, some entity from real world. You can think of it as of rough
6
+ # equivalent of Wikipedia article.
7
+ #
8
+ # Wiki has more details about entity [concept](https://github.com/molybdenum-99/reality/wiki/Entity)
9
+ # and [internals](https://github.com/molybdenum-99/reality/wiki/Entity%20internals).
10
+ #
11
+ # The easiest way to have an entity is to instantiate is as {Entity.load}
12
+ # (also aliased as `Reality.Entity()` method): you'll just have your
13
+ # entity already _loaded_ from all possible data sources (or `nil` if
14
+ # neither of them knows about it):
15
+ #
16
+ # ```ruby
17
+ # argentina = Reality::Entity('Argentina')
18
+ # # => #<Reality::Entity(Argentina):country>
19
+ # ```
20
+ # Then, you can use {#describe} to see what properties entity has, and
21
+ # call any of them by name, like `argentina.capital`.
22
+ #
23
+ # Or you can create not loaded entities with just {#initialize} (it
24
+ # may be useful when you want to further batch-load several of them
25
+ # through {List#load!}).
26
+ #
4
27
  class Entity
5
28
  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)
29
+
30
+ # Instance of Infoboxer's [page](http://www.rubydoc.info/gems/infoboxer/Infoboxer/MediaWiki/Page).
31
+ #
32
+ # Pretty useful on its own:
33
+ #
34
+ # ```ruby
35
+ # puts Reality::Entity('Argentina').wikipage.intro
36
+ # # Argentina (ˌɑrdʒənˈtiːnə; aɾxenˈtina), officially the Argentine Republic (República Argentina), is a federal republic....
37
+ # ```
38
+ #
39
+ # Refer to [Infoboxer's documentation](http://www.rubydoc.info/gems/infoboxer/Infoboxer/MediaWiki/Page)
40
+ # for details.
41
+ #
42
+ # @return [Infoboxer::MediaWiki::Page]
43
+ attr_reader :wikipage
44
+
45
+ # All values extracted from data sources, in structured form.
46
+ # You can pretty-previes them via {#describe}, as well as work with
47
+ # separate values with method call (see {#method_missing}).
48
+ #
49
+ # @return [Hash<Symbol, Object>]
50
+ attr_reader :values
51
+
52
+ # @private
53
+ attr_reader :wikipedia_type, :wikidata, :wikidata_id
54
+
55
+ # Creates new entity. Initially, entity is _not_ loaded from datasources,
56
+ # it's just a name. If you want to receive a loaded entity with one
57
+ # statement, take a look at {Entity.load}, which does `new` + `load!`
58
+ # under the hoods.
59
+ #
60
+ # ```
61
+ # e = Reality::Entity.new('Mississippi')
62
+ # # => #<Reality::Entity?(Mississippi)>
63
+ # e.loaded?
64
+ # # => false
65
+ # e.values
66
+ # # => {}
67
+ # ```
68
+ #
69
+ # @param name [String] Name of the entity you want. Basically, it's
70
+ # Wikipedia page name for some concept. Not-so-basically, please
71
+ # refer to [names explanation](https://github.com/molybdenum-99/reality/wiki/Entity#entity-names)
72
+ # in our wiki.
73
+ # @param wikidata_id [String] Used mostly internally (when not only
74
+ # entity name, but also wikidata id is known; this happens on loading
75
+ # entities by references from other entities).
76
+ def initialize(name, wikidata_id: nil)
11
77
  @name = name
12
- @wikipage, @wikidata, @wikidata_id = wikipage, wikidata, wikidata_id
78
+ @wikidata_id = wikidata_id
13
79
  @values = {}
14
-
15
- load! if load
16
- after_load if @wikipage
17
80
  end
18
81
 
82
+ # Entity name string. For not loaded entity returns the name by which it
83
+ # was created. For loaded, it's correct name of Wikipedia page:
84
+ #
85
+ # ```ruby
86
+ # e = Reality::Entity.new('Einstein')
87
+ # # => #<Reality::Entity?(Einstein)>
88
+ # e.name
89
+ # # => "Einstein"
90
+ # e.load!
91
+ # # => #<Reality::Entity(Albert Einstein)>
92
+ # e.name
93
+ # # => "Albert Einstein"
94
+ # ```
95
+ #
96
+ # @return [String]
19
97
  def name
20
98
  @wikipage ? @wikipage.title : @name
21
99
  end
22
100
 
101
+ # Returns entity brief representation. Things to note:
102
+ #
103
+ # ```
104
+ # #<Reality::Entity?(Argentina)>
105
+ # ^ ^
106
+ # | Entity name
107
+ # Sign of not
108
+ # loaded entity
109
+ #
110
+ # #<Reality::Entity(Argentina):country>
111
+ # ^ ^
112
+ # | Name of "additional type" (to be documented...)
113
+ # No question mark:
114
+ # entity is loaded
115
+ # ```
116
+ #
117
+ # @return [String]
23
118
  def inspect
24
119
  if @wikipedia_type && @wikipedia_type.symbol
25
120
  "#<#{self.class}#{loaded? ? '' : '?'}(#{name}):#{@wikipedia_type.symbol}>"
@@ -28,11 +123,27 @@ module Reality
28
123
  end
29
124
  end
30
125
 
31
- def _describe
32
- load! unless loaded?
33
- Util::Format.describe(inspect, values.map{|k,v| [k, v.inspect]})
34
- end
35
-
126
+ # Prints general object state and all properties with values
127
+ #
128
+ # Example:
129
+ #
130
+ # ```
131
+ # $> Reality::Entity.new('Mississippi').describe
132
+ # Output:
133
+ # -------------------------------
134
+ # <Reality::Entity(Mississippi)>
135
+ # -------------------------------
136
+ # capital: #<Reality::Entity?(Jackson)>
137
+ # coord: #<Reality::Geo::Coord(33°0′0″N,90°0′0″W)>
138
+ # country: #<Reality::Entity?(United States of America)>
139
+ # created_at: Wed, 10 Dec 1817
140
+ # located_in: #<Reality::Entity?(United States of America)>
141
+ # neighbours: #<Reality::List[Alabama?, Tennessee?, Louisiana?, Arkansas?]>
142
+ # official_website: "http://www.mississippi.gov"
143
+ # tz_offset: #<Reality::TZOffset(UTC-06:00)>
144
+ # ```
145
+ #
146
+ # @return [nil]
36
147
  def describe
37
148
  puts _describe
38
149
  nil
@@ -42,39 +153,70 @@ module Reality
42
153
  name
43
154
  end
44
155
 
156
+ # @private
45
157
  def to_s?
46
158
  # FIXME: fuuuuuuuu
47
159
  "#{name.include?(',') ? '"' + name + '"' : name}#{loaded? ? '' : '?'}"
48
160
  end
49
161
 
162
+ # Loads entity data from all external sources.
163
+ #
164
+ # Note that this method is called implicitly on {#method_missing},
165
+ # {#describe} or {#to_h}.
166
+ #
167
+ # Note also that if you need several entities to be loaded, its much
168
+ # more effective to have them grouped into {List} and batch-loaded
169
+ # via {List#load!}.
170
+ #
171
+ # @return [self]
50
172
  def load!
51
173
  if @wikidata_id
52
- @wikidata = Wikidata::Entity.fetch_by_id(@wikidata_id)
174
+ @wikidata = Wikidata::Entity.one_by_id(@wikidata_id)
53
175
  if @wikidata && @wikidata.en_wikipage
54
176
  @wikipage = Infoboxer.wikipedia.get(@wikidata.en_wikipage)
55
177
  end
56
178
  else
57
179
  @wikipage = Infoboxer.wikipedia.get(name)
58
- if @wikipage
59
- @wikidata = Wikidata::Entity.fetch(@wikipage.title).first
180
+ @wikidata = if @wikipage
181
+ Wikidata::Entity.one_by_wikititle(@wikipage.title)
182
+ else
183
+ Wikidata::Entity.one_by_label(name)
60
184
  end
61
185
  end
62
186
  after_load
63
187
  self
64
188
  end
65
189
 
190
+ # @private
66
191
  def setup!(wikipage: nil, wikidata: nil)
67
192
  @wikipage, @wikidata = wikipage, wikidata
68
- after_load if @wikipage
193
+ after_load if @wikipage || @wikidata
194
+ self
69
195
  end
70
196
 
197
+ # Returns `true` if entity is loaded already.
198
+ #
199
+ # @return [Boolean]
71
200
  def loaded?
72
- !!@wikipage
201
+ !!(@wikipage || @wikidata)
73
202
  end
74
203
 
204
+ # @private
75
205
  # Don't try to convert me!
76
206
  UNSUPPORTED_METHODS = [:to_hash, :to_ary, :to_a, :to_str, :to_int]
77
207
 
208
+ # Entity handles `method_missing` this way:
209
+ #
210
+ # * loads itself if it was not loaded;
211
+ # * returns one of {values} by method name.
212
+ #
213
+ # Note, that even if there's no value with required key, `method_missing`
214
+ # will return `nil` (and not through `NoMethodError` as someone may
215
+ # expect). That's because even supposedly homogenous entities may have different
216
+ # property sets, and typically you want to do something like
217
+ # `cities.map(&:area).compact.inject(:+)`, not handling exceptions
218
+ # about "this city has no 'area' property".
219
+ #
78
220
  def method_missing(sym, *arg, **opts, &block)
79
221
  if arg.empty? && opts.empty? && !block && sym !~ /[=?!]/ &&
80
222
  !UNSUPPORTED_METHODS.include?(sym)
@@ -92,28 +234,87 @@ module Reality
92
234
  end
93
235
  end
94
236
 
95
- def respond_to?(sym)
96
- sym !~ /[=?!]/ && !UNSUPPORTED_METHODS.include?(sym) || super
237
+ # @private
238
+ def respond_to?(sym, include_all = false)
239
+ sym !~ /[=?!]/ && !UNSUPPORTED_METHODS.include?(sym) || super(sym)
97
240
  end
98
241
 
99
242
  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
243
+ # Loads Entity from all datasources. Returns either loaded entity
244
+ # or `nil` if entity not found.
245
+ #
246
+ # @param name [String] See {#initialize} for explanations about
247
+ # entity names.
248
+ #
249
+ # @return [Entity, nil]
250
+ def load(name)
251
+ Entity.new(name).load!.tap{|entity|
252
+ return nil if !entity.loaded?
104
253
  }
105
254
  end
106
255
  end
107
256
 
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
257
+ # Converts Entity to hash, preserving only _core types_ (so, you can
258
+ # store this hash in YAML or JSON, for example). Some notes on conversion:
259
+ # * Entity name goes to `:name` key;
260
+ # * Rational values became floating point;
261
+ # * {Measure} and {Geo::Coord} became hashes;
262
+ # * other entities became: when loaded - hashes, when not loaded -
263
+ # just strings of entity name
264
+ #
265
+ # Example:
266
+ #
267
+ # ```ruby
268
+ # argentina = Reality::Entity('Argentina')
269
+ # # => #<Reality::Entity(Argentina):country>
270
+ # argentina.head_of_government.load!
271
+ # # => #<Reality::Entity(Mauricio Macri)>
272
+ # agentina.to_h
273
+ # # {:name=>"Argentina",
274
+ # # :long_name=>"Argentine Republic",
275
+ # # :area=>{:amount=>2780400.0, :unit=>"km²"},
276
+ # # :gdp_ppp=>{:amount=>964279000000.0, :unit=>"$"},
277
+ # # :population=>{:amount=>43417000.0, :unit=>"person"},
278
+ # # :head_of_government=>
279
+ # # {:name=>"Mauricio Macri",
280
+ # # :birthday=>"1959-02-08",
281
+ # # ....},
282
+ # # :country=>"Argentina",
283
+ # # :continent=>"South America",
284
+ # # :head_of_state=>"Mauricio Macri",
285
+ # # :capital=>"Buenos Aires",
286
+ # # :currency=>"peso",
287
+ # # :neighbours=>["Uruguay", "Brazil", "Chile", "Paraguay", "Bolivia"],
288
+ # # :tld=>".ar",
289
+ # # :adm_divisions=>["Buenos Aires", "Buenos Aires Province", ....],
290
+ # # :iso2_code=>"AR",
291
+ # # :iso3_code=>"ARG",
292
+ # # :part_of=>["Latin America"],
293
+ # # :tz_offset=>"-03:00",
294
+ # # :organizations=>["United Nations","Union of South American Nations","Mercosur",...],
295
+ # # :calling_code=>"+54",
296
+ # # :created_at=>"1816-01-01",
297
+ # # :highest_point=>"Aconcagua",
298
+ # # :coord=>{:lat=>-34.0, :lng=>-64.0},
299
+ # # :official_website=>"http://www.argentina.gob.ar/",
300
+ # # :gdp_nominal=>{:amount=>537659972702.0, :unit=>"$"}}
301
+ # #
302
+ # ```
303
+ #
304
+ # @return [Hash]
305
+ def to_h
306
+ load! unless loaded?
307
+ {name: name}.merge \
308
+ values.map{|k, v| [k.to_sym, Coercion.to_simple_type(v)]}.to_h
309
+ end
310
+
311
+ # Converts entity to JSON (same as `entity.to_h.to_json`)
312
+ #
313
+ # @see #to_h
314
+ # @return [String]
315
+ def to_json
316
+ to_h.to_json
317
+ end
117
318
 
118
319
  protected
119
320
 
@@ -131,22 +332,9 @@ module Reality
131
332
  end
132
333
  end
133
334
 
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
335
+ def _describe
336
+ load! unless loaded?
337
+ Util::Format.describe(inspect, values.map{|k,v| [k, v.inspect]})
149
338
  end
150
-
151
339
  end
152
340
  end